如何使用bash/sed脚本删除文本文件的第一行?

766

我需要使用bash脚本从一个巨大的文本文件中反复删除第一行。

目前我正在使用sed -i -e "1d" $FILE,但它需要约一分钟才能完成删除。

是否有更有效的方法来完成这个任务?


-i 代表什么意思? - cikatomo
4
它代表“内联编辑”,它使用生成的任何内容来编辑文件。 - drewrockshard
7
tailе‘Ҫд»ӨжҜ”sedе‘Ҫд»Өж…ўеҫ—еӨҡгҖӮtailйңҖиҰҒ13.5з§’пјҢиҖҢsedеҸӘйңҖиҰҒ0.85з§’гҖӮжҲ‘зҡ„ж–Ү件еӨ§зәҰжңү100MBпјҢеҢ…еҗ«зәҰ1зҷҫдёҮиЎҢж•°жҚ®гҖӮжҲ‘дҪҝз”Ёзҡ„жҳҜеёҰжңүSSDзҡ„MacBook Air 2013гҖӮ - jcsahnwaldt Reinstate Monica
20个回答

1354

尝试使用tail命令:

tail -n +2 "$FILE"

-n x: 打印最后的 x 行。例如,tail -n 5 将给出输入的最后 5 行。加号 + 反转参数并使 tail 打印除前 x-1 行之外的所有行。例如,tail -n +1 将打印整个文件,tail -n +2 打印除第一行外的所有内容等。

GNU tailsed 快得多。在 BSD 上也可以使用 tail,而 -n +2 标志在两个工具中是一致的。请参阅FreeBSDOS X手册以获取更多信息。

然而,在某些情况下,BSD 版本可能比 sed 慢得多。这让我很奇怪;tail 应该只需要逐行读取文件,而 sed 则需要执行涉及脚本解释、应用正则表达式等相当复杂的操作。

注意:您可能会想使用

# THIS WILL GIVE YOU AN EMPTY FILE!
tail -n +2 "$FILE" > "$FILE"

但这会给你一个空文件。原因是重定向(>)发生在shell调用tail之前:

  1. Shell截断文件$FILE
  2. Shell为tail创建一个新进程
  3. Shell将tail进程的标准输出重定向到$FILE
  4. tail从现在的空白$FILE中读取

如果你想要删除文件内的第一行,你应该使用:

tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"

&& 会确保在出现问题时文件不被覆盖。


3
根据这个网址 http://ss64.com/bash/tail.html ,当使用 BSD 的 'tail' 命令时,使用 -r 选项时,典型的缓冲区默认为 32k。系统中可能有某个缓冲设置吗?或者 -n 是一个 32 位带符号数? - Yzmir Ramirez
43
@Eddie:用户869097说当单行文本达到15MB或以上时,tail命令会失效。只要行数较短,tail命令适用于任何文件大小。 - Aaron Digulla
2
哎呀,谢谢你纠正我。哇,15mb的网速... 我甚至都无法想象这样的情况。 - Eddie
2
@Dreampuf:sed有一个当前行的内部缓冲区,而tail只需记住最后N个换行符的偏移量即可(请注意,我实际上没有查看源代码)。 - Aaron Digulla
13
我本来想同意@JonaChristopherSahnwaldt的观点 - tail 命令比 sed 变体慢得多,慢一个数量级。我正在测试一个500,000K行的文件(每行不超过50个字符)。然而,后来我意识到我使用的是FreeBSD版本的tail(默认情况下附带在OS X中)。当我切换到GNU版本的tail时,tail命令比sed命令(以及GNU sed命令)快了10倍。如果你正在使用GNU版本,AaronDigulla是正确的。 - dancow
显示剩余18条评论

336

使用sed,模式'1d'将删除第一行。此外,可以使用-i标志在原地更新文件。1

sed -i '1d' filename

1 sed -i 自动创建一个带有所需更改的临时文件,然后替换原始文件。


16
这个方法一定能行,而且应该是最佳答案! - xtheking
13
请记住,使用sed进行原地编辑时,Mac需要提供后缀。因此,请使用-i.bak运行上述命令。 - mjp
12
注意:要删除几行,请使用 sed -i '1,2d' filename 命令。 - The Godfather
8
这个版本比"tail -n +2" 更易读且更通用。不确定为什么它不是最佳答案。 - Luke Davis
3
在Ubuntu(GNU)上可以工作,但对于OS X(BSD),我不得不将其更改为sed -i'' '1d' filename。根据https://dev59.com/FWQn5IYBdhLWcg3wiXud。 - Ahmad Abdelghany
显示剩余7条评论

84

对于那些使用非GNU的SunOS操作系统的用户,以下代码将会有所帮助:

sed '1d' test.dat > tmp.dat 

50
有趣的人口统计学特征。 - captain
1
@ValerioBozz 在十年后重新访问这条评论感觉有点奇怪哈哈。我甚至都不记得了。但是我只是指出这个答案是针对SunOS的,而SunOS最后一次发布是在1998年。很少有人使用它。 - captain

22

你可以轻松使用以下方法来实现:

cat filename | sed 1d > filename_without_first_line

在命令行上可以使用sed命令,或者使用sed的就地模式(-i标志)永久删除文件的第一行:

sed -i 1d <filename>

3
-i 选项技术上需要一个参数来指定文件备份时要使用的后缀名(例如,sed -I .bak 1d filename 会创建一个名为 filename.bak 的副本,其中包含原始文件的第一行)。虽然 GNU sed 允许您指定不带参数的 -i 来跳过备份,但是在 macOS 上找到的 BSD sed 需要一个空字符串作为单独的 shell 单词参数(例如,sed -i '' ...)。 - Mark Reed

16

sponge 工具 避免了需要处理临时文件的麻烦:

tail -n +2 "$FILE" | sponge "$FILE"

1
sponge确实比已接受的解决方案(tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE")更加干净和健壮。 - Jealie
这是我找到的唯一解决方案,用于更改系统文件(在Debian docker镜像上)。其他解决方案尝试写入该文件时由于“设备或资源繁忙”错误而失败。 - FedFranz
2
但是 sponge 会在内存中缓存整个文件吗?如果文件大小达到几百GB,那就无法工作了。 - OrangeDog
1
只要文件系统可以存储它,sponge 就会吸收它,因为它使用 /tmp 文件作为中间步骤,然后用它来替换原始文件。 - agc

15
不会更有效率了,你可以编写一个 C 程序,它可能会比 sed 更快(启动时间短且处理参数),但随着文件变大(如果需要一分钟的话,我想它们应该很大),它也可能趋向于与 sed 相同的速度。
但是你的问题和许多其他问题一样,存在预设解决方案的问题。如果你能详细告诉我们你想做什么而不是如何做,我们可能会建议更好的选择。
例如,如果这是一个由某个程序 B 处理的文件 A,一个解决方案是不要删除第一行,而是修改程序 B 以不同的方式处理它。
假设你的所有程序都附加到文件 A 中,并且程序 B 当前在删除第一行之前读取并处理第一行。
你可以重新设计程序 B,使其不尝试删除第一行,而是维护一个持久的(可能基于文件的)偏移量到文件 A 中,以便下次运行时可以跳转到该偏移量,处理那里的行,并更新偏移量。
然后,在一个安静的时间(午夜?),它可以对文件 A 进行特殊处理,以删除当前已处理的所有行,并将偏移量设置为 0。
对于一个程序来说,打开和查找文件肯定比打开和重写文件更快。当然,这个讨论假设你有控制程序 B,如果是这种情况,我不知道是否成立,但如果你提供更多信息,可能会有其他可能的解决方案。

我认为OP试图实现的是让我找到这个问题的原因。我有10个CSV文件,每个文件有500k行。每个文件的第一行都有相同的标题行。我正在将这些文件合并成一个文件,然后将其导入到数据库中,让数据库从第一行创建列名。显然,我不希望在文件2-10中重复该行。 - d-b
4
在这种情况下,awk FNR-1 *.csv 可能更快。 - jinawee

13
如果您想直接修改文件,则始终可以使用原始的ed而不是其streaming后继者sed
ed "$FILE" <<<$'1d\nwq\n'

ed 命令是最初的UNIX文本编辑器,甚至在全屏终端和图形工作站出现之前就已经存在了。 ex 编辑器是基于 ed 的扩展版本,最著名的用法是在 vi 冒号提示符下输入命令。因此,许多相同的命令也可以在 ex 中使用。虽然 ed 通常用于交互式使用,但也可以通过向它发送一系列命令来批处理使用,这就是这个解决方案的做法。

序列 <<<$'1d\nwq\n' 利用现代Shell对Here-strings(<<<)和ANSI引用($'...')的支持,将两行命令作为输入传递给 ed 命令:第一行是 1d,表示删除第一行;第二行是 wq,表示先写回到磁盘上,再退出编辑会话


但是你必须将整个文件读入内存,如果文件大小达到数百GB,则无法正常工作。 - OrangeDog
1
可以在Mac上无需任何操作(zsh)即可运行。 - xpagesbeast

11

你可以在原地编辑文件:只需使用Perl的-i标识,像这样:

perl -ni -e 'print unless $. == 1' filename.txt

这会使得第一行消失,就像您想要的那样。Perl需要读取并复制整个文件,但它会安排输出保存在原始文件名下。


11

正如Pax所说,你可能无法做得比这更快。原因是几乎没有文件系统支持从文件开头截断,因此这将是一个O(n)操作,其中n是文件的大小。然而,您可以用相同数量的字节(也许是空格或注释)覆盖第一行,这样可以更快地完成,具体取决于您要做什么(顺便问一下,您到底想做什么?)。


1
Re "...almost no filesystems that support truncating...": 这很有趣;请考虑包含一个括号注释,命名这样的文件系统。 - agc
5
@agc: 虽然与当前无关,但我在70年代的第一份工作是与Quadex合作,这是一家小型创业公司(现已倒闭,并且与现在使用该名称的两家公司没有关系)。他们有一个文件系统,允许在文件的开头或结尾添加或删除内容,主要用于通过将窗口上方和下方放入文件中实现小于3KB的编辑。它本身没有名字,只是QMOS(Quadex Multiuser Operating System)的一部分。在LSI-11/02上,通常只有2-3个用户,内存不到64KB,通常会使用几个RX01类型的8英寸软盘,每个软盘容量为250KB。 :-) - dave_thompson_085

7

应该显示除第一行以外的所有行:

cat textfile.txt | tail -n +2

4
你应该执行命令 "tail -n +2 textfile.txt"。 - niglesias
6
我不同意“无用的cat使用”这一说法,因为它明确表明此解决方案适用于管道内容而不仅仅是文件。 - Titou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接