我有无数的文件需要减小大小。我发现大多数(不是全部)文件都有一个结束部分,可以在不丢失信息的情况下进行剪切:
Data 1
Data 2
something_unimportant_here END DATA
Rubbish 1
Rubbish 2
如何通过删除包含“END DATA”的行以及所有后续内容来编辑文件(因此结束,全部),就地,仅更改包含该模式的文件,从而最大限度地减少对磁盘的写访问(很多很多文件和慢速磁盘)。
如果可能的话,我想向文件添加一个新的最后一行(我自己的结束标记),以便文件的语法保持正确——同样,仅在包含该模式的文件中。
我正在考虑使用ed
,比如
echo ',s/END DATA/ ???? '\\n'q'\\n'wq' | ed "$file"
但似乎无法管理???部分正确。
预期输出:
Data 1
Data 2
NEW END
答案1
您应该能够通过直接截断文件来做到这一点,而不必像sed -i
/ perl -i
/ ed
/ gawk -i /usr/share/awk/inplace.awk
¹ 那样写入文件的新副本。使用perl
:
find . -name '*.txt' -type f -exec perl -ne '
BEGIN{@ARGV=map{"+<$_"}@ARGV} # open files in read+write mode in the
# while(<>) loop implied by -n
if (/END DATA/) {
seek ARGV,-length,1; # back to beginning of matching line
print ARGV "NEW END\n";
truncate ARGV, tell ARGV;
close ARGV; # skip to next file
}' {} +
这最大限度地减少了 I/O,因为perl
一旦找到匹配项就会停止读取,并且这 NEW END\n
是它唯一写入的内容。它还会就地写入,因此文件元数据(所有权、权限、acls、稀疏性...)会被保留,并且硬链接不会被破坏。
我们-exec {} +
还可以最大限度地减少调用次数perl
。
^不使用-i inplace
as尝试首先从当前工作目录gawk
加载inplace
扩展(asinplace
或),有人可能已经在其中植入了恶意软件。随系统提供的扩展inplace.awk
的路径可能会有所不同,请参阅输出inplace
gawk
gawk 'BEGIN{print ENVIRON["AWKPATH"]}'
答案2
听起来您正在寻找的命令序列是
/END DATA/,$d
q
.a
NEW END
.
wq
或作为单行
printf '%s\n' '/END DATA/,$d' 'q' '.a' 'NEW END' '.' 'wq'
(您可以替换wq
为,p
进行测试。)
前任。给定
$ cat file
Data 1
Data 2
something_unimportant_here END DATA
Rubbish 1
Rubbish 2
然后
$ printf '%s\n' '/END DATA/,$d' 'q' '.a' 'NEW END' '.' 'wq' | ed -s file
给出
$ cat file
Data 1
Data 2
NEW END
答案3
与GNU grep
和GNU sed
grep -lZ 'END DATA' *.txt | xargs -0 sed -i -e '/END DATA/,${//i foo' -e 'd}'
其中*.txt
假设所有文件都位于以.txt
扩展名结尾的当前目录中。如果需要递归搜索文件,GNU grep
也支持-r/-R
选项。
/END DATA/,$
运营线路范围
//i foo
这里//
将匹配以前使用的正则表达式,/END DATA/
即i
命令将根据需要添加新的结束标记
由于i
命令必须用换行符分隔,因此-e
选项用于分隔d
命令以删除与范围匹配的所有行
作为替代方案,您也可以使用此方法,但一次只会将一个文件传递到sed
:
grep -lZ 'END DATA' *.txt | xargs -0 -n1 sed -i -e '/END DATA/{i foo' -e 'Q}'
答案4
这个python
3.8 解决方案松散地基于 Stephane 的就地解决方案truncate
解决方案有几个区别 1. 代码不依赖外部实用程序进行目录遍历 2. 文件是内存映射的,以便于定位END DATA
字符串
将代码放入.py
文件中并将目录名称作为参数传递
import mmap
import os
import sys
from contextlib import closing
def yield_all_files(dir_):
for root, dir_, files in os.walk(dir_):
yield from (os.path.join(root, file_) for file_ in files if file_.endswith('.txt'))
if __name__ == '__main__':
old = b'END DATA'
new = b'NEW END\n'
dir_ = sys.argv[1]
for file_ in yield_all_files(dir_):
with open(file_, mode='r+b') as f:
with closing(mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_WRITE)) as mm:
if (loc := mm.find(old)) > -1:
mm.seek(loc)
mm.write(new)
mm.resize(mm.tell())