689

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

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

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

4

17 回答 17

1243

试试尾巴

tail -n +2 "$FILE"

-n x: 只打印最后x几行。tail -n 5会给你输入的最后 5 行。+符号类型反转参数并tail打印除第一x-1行以外的任何内容。tail -n +1将打印整个文件,tail -n +2除第一行外的所有内容等。

GNUtailsed. tail在 BSD 上也可以使用,并且-n +2标志在两个工具中是一致的。查看FreeBSDOS X手册页以获取更多信息。

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

注意:您可能很想使用

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

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

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

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

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

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

于 2008-12-04T08:55:16.587 回答
264

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

sed -i '1d' filename
于 2014-11-24T07:10:07.467 回答
80

对于那些使用非 GNU 的 SunOS 的人,以下代码将有所帮助:

sed '1d' test.dat > tmp.dat 
于 2013-02-19T07:32:38.597 回答
18

您可以通过以下方式轻松做到这一点:

cat filename | sed 1d > filename_without_first_line

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

sed -i 1d <filename>
于 2018-11-22T14:34:38.633 回答
16

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

但是您的问题与许多其他问题存在相同的问题,因为它预先假定了解决方案。如果您要详细告诉我们要做什么而不是如何做,我们可能会提出更好的选择。

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

假设您的所有程序都附加到此文件 A 和程序 B 当前读取并处理第一行,然后再删除它。

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

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

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

于 2008-12-04T03:19:12.163 回答
11

sponge实用程序避免了处理临时文件的需要:

tail -n +2 "$FILE" | sponge "$FILE"
于 2016-08-05T20:14:14.590 回答
11

如果你想修改文件,你总是可以使用原始文件ed而不是它的 s处理后继sed

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

ed命令是最初的 UNIX 文本编辑器,甚至还没有全屏终端,更不用说图形工作站了。编辑器,最广为人知的ex是您在冒号提示符下键入时使用的内容vi,它是 的扩展版本ed,因此许多相同的命令都可以工作。虽然ed旨在以交互方式使用,但它也可以通过向其发送一串命令以批处理模式使用,这就是此解决方案的作用。

该序列<<<$'1d\nwq\n'利用现代 shell 对 here-strings ( )<<<和 ANSI 引号 ( $'... ) 的支持,'将输入提供给ed由两行组成的命令:删除第1行,然后将文件回输出到磁盘,然后退出编辑会话。1dwq

于 2018-05-15T18:57:22.533 回答
10

正如 Pax 所说,您可能不会比这更快。原因是几乎没有文件系统支持从文件开头截断,所以这将是一个 O( n) 操作,其中n文件的大小是多少。您可以更快地做是用相同数量的字节(可能带有空格或注释)覆盖第一行,这可能对您有用,具体取决于您正在尝试做什么(顺便说一句?)。

于 2008-12-04T03:48:49.823 回答
10

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

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

如您所问,这会使第一行消失。Perl 将需要读取和复制整个文件,但它会安排将输出保存在原始文件的名称下。

于 2013-02-15T21:40:27.087 回答
6

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

cat textfile.txt | tail -n +2
于 2016-09-29T07:42:01.580 回答
6

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

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

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

于 2017-10-17T14:29:25.173 回答
4

使用 csplit 怎么样?

man csplit
csplit -k file 1 '{1}'
于 2009-03-04T16:08:02.983 回答
3

这一个班轮将做:

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

它可以工作,因为tail在之前执行echo然后文件被解锁,因此不需要临时文件。

于 2020-03-09T20:06:34.753 回答
1

由于听起来我无法加快删除速度,我认为一个好的方法可能是像这样批量处理文件:

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

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

(file1 包含几行 sql 代码)

于 2008-12-04T03:40:33.633 回答
0

如果您要做的是在失败后恢复,您可以构建一个包含您迄今为止所做的文件的文件。

if [[ -f $tmpf ]] ; then
    rm -f $tmpf
fi
cat $srcf |
    while read line ; do
        # process line
        echo "$line" >> $tmpf
    done
于 2009-11-14T01:42:44.770 回答
0

基于其他 3 个答案,我想出了在我的 Mac OSx bash shell 中完美运行的语法:

line=$(head -n1 list.txt && echo "$(tail -n +2 list.txt)" > list.txt)

测试用例:

~> printf "Line #%2d\n" {1..3} > list.txt
~> cat list.txt
Line # 1
Line # 2
Line # 3
~> line=$(head -n1 list.txt && echo "$(tail -n +2 list.txt)" > list.txt)
~> echo $line
Line # 1
~> cat list.txt
Line # 2
Line # 3
于 2021-11-06T20:12:31.720 回答
-1

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

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

于 2008-12-04T03:50:44.440 回答