Las*_*las 7 sed text-processing
一个相关的问题是here。
我经常需要通过从中间删除几行来编辑大文件。我知道我想删除哪些行,我通常会执行以下操作:
sed "linenum1,linenum2 d" input.txt > input.temp
Run Code Online (Sandbox Code Playgroud)
或通过添加 -i 选项内联。由于我知道行号,是否有避免流编辑并删除特定行的命令?input.txt 可以大到 50 GB。
Sté*_*las 10
为了避免写入文件副本,您可以做的是将文件写入自身,例如:
{
sed "$l1,$l2 d" < file
perl -le 'truncate STDOUT, tell STDOUT'
} 1<> file
Run Code Online (Sandbox Code Playgroud)
危险,因为那里没有备份副本。
或者避免sed
,窃取 manatwork 的部分想法:
{
head -n "$(($l1 - 1))"
head -n "$(($l2 - $l1 + 1))" > /dev/null
cat
perl -le 'truncate STDOUT, tell STDOUT'
} < file 1<> file
Run Code Online (Sandbox Code Playgroud)
这仍然可以改进,因为您在不需要的情况下覆盖了前l1 - 1行,但是避免它意味着更多的编程,例如做所有perl
可能最终降低效率的事情:
perl -ne 'BEGIN{($l1,$l2) = ($ENV{"l1"}, $ENV{"l2"})}
if ($. == $l1) {$s = tell(STDIN) - length; next}
if ($. == $l2) {seek STDOUT, $s, 0; $/ = \32768; next}
if ($. > $l2) {print}
END {truncate STDOUT, tell STDOUT}' < file 1<> file
Run Code Online (Sandbox Code Playgroud)
从输出中删除 1000000 到 1000050 行的一些时间seq 1e7
:
sed -i "$l1,$l2 d" file
: 16.2s它们都以相同的原理工作:我们打开文件的两个文件描述符,一个在只读模式 (0) 下使用< file
short for 0< file
,另一个在读写模式 (1) 下使用1<> file
( <> file
would be 0<> file
)。这些文件描述符指向两个打开的文件描述,每个描述在文件中都有一个当前光标位置与之关联。
例如,在第二种解决方案中,第一个将从 fd 0head -n "$(($l1 - 1))"
读取$l1 - 1
数据行并将该数据写入 fd 1。因此在该命令结束时,与 fds 0 和 1 关联的打开文件描述上的光标将位于$l1
第 th 行的开始。
然后,在head -n "$(($l2 - $l1 + 1))" > /dev/null
,head
将读取$l2 - $l1 + 1
从同一行打开文件描述通过其FD 0,其仍与其关联,等等FD 0光标将移动到后的行的开头$l2
之一。
但是它的 fd 1 已被重定向到/dev/null
,因此在写入 fd 1 时,它不会在的 fd 1指向的打开文件描述中移动光标{...}
。
因此,在启动时cat
,fd 0 指向的打开文件描述上的光标将位于 之后下一行的开头$l2
,而 fd 1 上的光标仍将位于$l1
第 th 行的开头。或者换句话说,第二个head
将跳过这些行以删除输入而不是输出。现在cat
将$l1
用下一行覆盖第th 行$l2
,依此类推。
cat
当它到达 fd 0 上的文件末尾时将返回。但是 fd 1 将指向文件中尚未被覆盖的某个地方。那部分必须消失,它对应于现在转移到文件末尾的已删除行所占用的空间。我们需要的是在 fd 1 现在指向的确切位置截断文件。
这是通过ftruncate
系统调用完成的。不幸的是,没有标准的 Unix 实用程序可以做到这一点,所以我们求助于perl
. tell STDOUT
让我们用FD 1相关的当前光标位置,我们在截断,使用Perl的接口偏移文件ftruncate
系统调用:truncate
。
在第三个解决方案中,我们head
用一个lseek
系统调用替换了第一个命令对 fd 1 的写入。
使用sed
是一个很好的方法:很明显,它流式传输文件(长文件没有问题),并且可以很容易地推广以做更多的事情。但是,如果您想要一种简单的就地编辑文件的方法,最简单的方法是使用ed
or ex
:
(echo 10,31d; echo wq) | ed input.txt
Run Code Online (Sandbox Code Playgroud)
一种更好的方法,保证可以处理无限大小的文件(并且只要您的 RAM 允许行)是以下perl
单行,它可以在适当的位置编辑文件:
perl -n -i -e 'print if $. < 10 || $. > 31' input.txt
Run Code Online (Sandbox Code Playgroud)
解释:
-n
: 将脚本应用到每一行。不产生其他输出。
-i
:就地编辑文件(用于-i.bck
备份)。
-e ...
:打印每一行,除了第 10 到 31 行。