如何从出现某种模式的行开始剪切文件?

如何从出现某种模式的行开始剪切文件?

我有无数的文件需要减小大小。我发现大多数(不是全部)文件都有一个结束部分,可以在不丢失信息的情况下进行剪切:

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 inplaceas尝试首先从当前工作目录gawk加载inplace扩展(asinplace或),有人可能已经在其中植入了恶意软件。随系统提供的扩展inplace.awk的路径可能会有所不同,请参阅输出inplacegawkgawk '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 grepGNU 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

这个python3.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()) 

相关内容