有没有更快的方法从文件中删除一行(给定行号)?

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
  • 第一种解决方案:1.25s
  • 第二种解决方案:0.057s
  • 第三种解决方案:0.48s

它们都以相同的原理工作:我们打开文件的两个文件描述符,一个在只读模式 (0) 下使用< fileshort for 0< file,另一个在读写模式 (1) 下使用1<> file( <> filewould be 0<> file)。这些文件描述符指向两个打开的文件描述,每个描述在文件中都有一个当前光标位置与之关联。

例如,在第二种解决方案中,第一个将从 fd 0head -n "$(($l1 - 1))"读取$l1 - 1数据行并将该数据写入 fd 1。因此在该命令结束时,与 fds 0 和 1 关联的打开文件描述上的光标将位于$l1第 th 行的开始。

然后,在head -n "$(($l2 - $l1 + 1))" > /dev/nullhead将读取$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 的写入。


ale*_*xis 6

使用sed是一个很好的方法:很明显,它流式传输文件(长文件没有问题),并且可以很容易地推广以做更多的事情。但是,如果您想要一种简单的就地编辑文件的方法最简单的方法是使用edor 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 行。

  • 但是,我认为 `ex`(elvis 2.2.0)更适合大文件:受 CPU 限制,使用的内存很少。`ed` (1.6) 用完了我的 9 GB 文件的内存(使用了超过 2 GB 的内存)。这解释了为什么您说对无限大小的文件使用 sed 或 perl。;-) (2认同)