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


554

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

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

有没有更有效的方法来实现这一目标?


-i代表什么?
cikatomo 2013年

4
@cikatomo:它代表内联编辑-它使用您生成的任何内容来编辑文件。
drewrockshard

4
尾巴比sed慢得多。尾巴需要13.5s,sed需要0.85s。我的文件有〜1M行,〜100MB。配备SSD的MacBook Air 2013。
jcsahnwaldt说GoFundMonica '16

Answers:


1029

试试tail

tail -n +2 "$FILE"

-n x:仅打印最后x几行。tail -n 5将为您提供输入的最后5行。该+标志那种反转的争论,使tail打印任何东西,但第一x-1线。tail -n +1将打印整个文件,tail -n +2除了第一行以外的所有内容,等等。

GNU tail比.NET 快得多sedtail在BSD上也可用,并且-n +2标记在两个工具之间都一致。有关更多信息,请参见FreeBSDOS X手册页。

BSD版本可能比慢得多sed。我想知道他们是如何做到的;tail应该只逐行读取文件,同时sed执行相当复杂的操作,包括解释脚本,应用正则表达式等。

注意:您可能会想使用

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

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

  1. Shell截断文件 $FILE
  2. 壳牌为 tail
  3. Shell将tail流程的标准输出重定向到$FILE
  4. tail 从现在的空读取 $FILE

如果要删除文件中的第一行,则应使用:

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

&&将确保该文件不被覆盖时,有一个问题。


3
根据此ss64.com/bash/tail.html,当将BSD'tail '与该-r选项一起使用时,典型的缓冲区默认为32k 。也许系统中某处有缓冲区设置?还是-n32位带符号的数字?
伊兹密尔·拉米雷斯

41
@Eddie:user869097说,当条线大于或等于15Mb 时,它将不起作用。只要行更短,tail就适用于任何文件大小。
亚伦·迪古拉

6
你能解释这些论点吗?
Dreampuf

17
@Dreampuf-手册页中的内容:-n N means output the last N lines, instead of the last 10; or use +N to output lines starting with the Nth
Sheppard将于

11
我本来同意@JonaChristopherSahnwaldt的观点-尾部比sed变体慢很多,而且要慢一个数量级。我正在500,000K行的文件中对其进行测试(每行不超过50个字符)。但是,然后我意识到我正在使用的是tail的FreeBSD版本(默认情况下是OS X附带的)。当我切换到GNU tail时,tail调用比sed调用(以及GNU sed调用)快10倍。如果您使用的是GNU,则AaronDigulla在这里是正确的。
丹阮

179

您可以使用-i来更新文件,而无需使用'>'运算符。以下命令将从文件中删除第一行并将其保存到文件中。

sed -i '1d' filename

1
我收到错误消息:unterminated transform source string
Daniel Kobe 2015年

10
这每次都有效,应该确实是最佳答案!
xtheking

4
请记住,Mac使用在本地编辑中使用sed时需要提供一个后缀。因此,请使用-i.bak
mjp

3
只需注意-删除多行内容sed -i '1,2d' filename
教父

4
这个版本确实比更具可读性和通用性tail -n +2。不知道为什么它不是最佳答案。
卢克·戴维斯


17

不,这与您将要获得的效率差不多。您可以编写一个C程序,该程序可以更快地完成工作(更少的启动时间和处理参数),但是随着文件变大,它可能趋向于与sed相同的速度(如果花一分钟,我认为它们会变大) )。

但是您的问题与其他许多问题一样面临着同样的问题,因为它预先提出了解决方案。如果你要详细告诉我们什么你想要做而不是如何,我们也许能够提出更好的选择。

例如,如果这是其他程序B处理的文件A,则一种解决方案是不剥离第一行,而修改程序B以不同方式处理它。

假设您的所有程序都附加到此文件A,并且程序B当前在删除第一行之前对其进行读取和处理。

您可以重新设计程序B,以便它不会尝试删除第一行,而是在文件A中保留一个持久的(可能是基于文件的)偏移量,以便在下次运行时可以查找该偏移量,然后进行处理。那里的线,并更新偏移量。

然后,在安静的时间(午夜?),它可以对文件A进行特殊处理,以删除当前处理的所有行并将偏移量设置回0。

程序打开和查找文件肯定比打开并重写更快。当然,该讨论假定您已控制程序B。我不知道是这种情况,但是如果您提供更多信息,可能还有其他可能的解决方案。


我认为OP正在尝试实现促使我找到此问题的原因。我有10个CSV文件,每个文件有50万行。每个文件的标题行与第一行相同。我正在把这些文件合并为一个文件,然后将它们导入数据库,让数据库从第一行创建列名。显然我不希望在文件2-10中重复该行。
db

1
@db在这种情况下,awk FNR-1 *.csv可能更快。
jinawee

10

可以就地编辑文件:只需使用perl的-i标志,如下所示:

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

根据您的要求,这使得第一行消失了。Perl将需要读取和复制整个文件,但是它安排将输出保存为原始文件的名称。


10

您可以轻松地做到这一点:

cat filename | sed 1d > filename_without_first_line

在命令行上;或要永久删除文件的第一行,请使用sed的就地模式和以下-i标志:

sed -i 1d <filename>

9

正如Pax所说,您可能不会比这更快。原因是几乎没有文件系统支持从文件开头截断,因此这将是O(n)操作,其中n文件的大小是。你可以做,虽然速度是覆盖具有相同的字节数(也许用空格或注释),这可能会为您取决于正是你正在尝试做的工作第一线(那是什么来着?)。


Re “ ...几乎没有支持截断的文件系统...”:很有意思;请考虑包括一个命名此类文件系统的括号。
AGC

1
@agc:现在无关紧要,但是我在70年代的第一份工作是与Quadex(一家小型创业公司(现已消失,与现在使用该名称的两家公司无关))合作。他们有一个允许在文件的开头或结尾添加删除的文件系统,该文件系统主要用于通过在文件中放置上下窗口来实现少于3KB的编辑。它没有自己的名字,它只是QMOS(Quadex多用户操作系统)的一部分。(“ Multi”在LSI-11 / 02上通常为2-3,具有64KB以下的RAM,通常为一些RX01型8“软盘,每个250KB。):-)
dave_thompson_085

9

sponge UTIL避免了杂耍一个临时文件的需要:

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

sponge确实比接受的解决方案更清洁,更可靠(tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
Jealie

1
应该明确的是,“海绵”要求安装“ moreutils”软件包。
FedFranzoni

这是唯一对我更改系统文件(在Debian docker映像上)的解决方案。尝试写入文件时,其他解决方案由于“设备或资源繁忙”错误而失败。
FedFranzoni

但是是否sponge将整个文件缓冲在内存中?如果只有数百GB,那将是行不通的。
OrangeDog

@OrangeDog,只要文件系统可以存储它,sponge就将其吸收,因为它使用/ tmp文件作为中间步骤,然后用于替换原始文件。
agc

8

如果要修改到位的文件,你总是可以使用原始ed的,而不是它的小号 treaming继任者sed

ed "$FILE" <<<$'1d\nwq\n'

ed命令是原始的UNIX文本编辑器,甚至还没有全屏终端,而图形工作站则少得多。在ex编辑器中,最有名的你使用的是什么类型时,在结肠中的提示vi,是一个的趋向版本ed,所以很多相同的命令工作。尽管ed本意是要交互使用,但也可以通过向其发送一串命令来以批处理方式使用它,这就是该解决方案的作用。

序列<<<$'1d\nwq\n'利用了bash的支持,这里串(<<<)和POSIX引号($'... '),以饲料投入到ed由两行命令:1d,其中d eletes行1,然后wq,这W¯¯仪式的文件重新出磁盘,然后q UITS编辑会话。


这很优雅。+1
Armin

但是您必须将整个文件读入内存,如果文件大小为数百GB,则将无法正常工作。
OrangeDog

5

应该显示第一行以外的行:

cat textfile.txt | tail -n +2

4
-您应该执行“ tail -n +2 textfile.txt”
niglesias

5
@niglesiais我不同意“猫的无用使用”,因为它清楚表明此解决方案适用于管道内容,而不仅仅是文件。
Titou

5

可以使用vim来做到这一点:

vim -u NONE +'1d' +'wq!' /tmp/test.txt

这应该更快,因为vim在处理时不会读取整个文件。


+wq!如果您的外壳是bash ,则可能需要引用。可能不是因为!单词不是开头,而是养成引用事物的习惯可能对所有人都有利。(并且,如果您不通过不必要的引用来提高效率,则不需要在两者之间都使用引号1d。)
Mark Reed

vim 确实需要读取整个文件。事实上,如果文件大于内存(如本问题所述),vim会读取整个文件并将其(或大部分)写入临时文件,并在编辑后将其全部写回(永久文件)。我不知道您认为没有这个怎么可能。
dave_thompson_085 '19

4

如何使用csplit?

man csplit
csplit -k file 1 '{1}'

此语法也可以使用,但只会生成两个输出文件,而不是三个:csplit file /^.*$/1。或更简单地说:csplit file //1。或更简单地说:csplit file 2
Marco Roy

1

由于听起来我无法加快删除速度,所以我认为一种好的方法可能是按以下方式批量处理文件:

While file1 not empty
  file2 = head -n1000 file1
  process file2
  sed -i -e "1000d" file1
end

这样做的缺点是,如果程序在中间被杀死(或者如果其中有一些不好的sql,导致“进程”部分死亡或锁定),则会跳过或处理两次行。

(file1包含几行sql代码)


第一行包含什么?您可以像我在帖子中建议的那样用sql注释覆盖它吗?
罗伯特·格兰伯

0

如果您要执行的操作是在故障后恢复,则可以构建一个文件,该文件具有到目前为止已完成的操作。

if [[ -f $tmpf ]] ; then
    rm -f $tmpf
fi
cat $srcf |
    while read line ; do
        # process line
        echo "$line" >> $tmpf
    done

0

这一条班轮将做:

echo "$(tail -n +2 "$FILE")" > "$FILE"

它可以工作,因为它tailecho在文件执行之前执行的,然后将其解锁,因此不需要临时文件。


-1

是否会在N-1行上使用tail并将其定向到文件中,然后删除旧文件,然后将新文件重命名为旧名称呢?

如果我以编程方式执行此操作,则在读取每一行后,我将通读文件,并记住文件偏移量,因此我可以返回该位置以读取其中少一行的文件。


第一个解决方案与Brent现在所做的基本上相同。我不理解您的编程方法,只需要删除第一行,您只需读取并丢弃第一行,然后将其余的行复制到另一个文件中,该文件再次与sed和tail方法相同。
罗伯特·格兰伯

第二种解决方案的含义是文件不会每次都被第一行缩小。该程序只是对其进行处理,就像它已经缩小一样,但是每次都从下一行开始
EvilTeach

我仍然不明白您的第二个解决方案是什么。
罗伯特·格兰伯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.