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

How can I remove the first line of a text file using bash/sed script?

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

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

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


试试尾巴:

1
tail -n +2"$FILE"

-n x:只需打印最后的x行。 tail -n 5将为您提供输入的最后5行。 +符号类型反转参数并使tail打印除第一个x-1行之外的任何内容。 tail -n +1将打印整个文件,tail -n +2除第一行外的所有内容,等等。

GNU tailsed快得多。 tail也可在BSD上使用,-n +2标志在两个工具中都是一致的。查看FreeBSD或OS X手册页了解更多信息。

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

注意:您可能很想使用

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

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

  • Shell截断文件$FILE
  • Shell为tail创建了一个新进程
  • Shell将tail进程的stdout重定向到$FILE
  • tail从现在为空的$FILE读取
  • 如果要删除文件中的第一行,则应使用:

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

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


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

    1
    sed -i '1d' filename


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

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


    不,那就像你要获得的那样高效。您可以编写一个C程序,它可以更快地完成工作(减少启动时间和处理参数)但它可能会趋向于与文件变大的sed相同的速度(并且我认为如果它需要一分钟就会很大)。

    但是你的问题与许多其他问题一样,因为它预先设定了解决方案。如果你要详细告诉我们你要做什么而不是如何做,我们可能会建议一个更好的选择。

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

    假设所有程序都附加到此文件A,程序B当前在删除它之前读取并处理第一行。

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

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

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


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

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

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


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


    如果要修改文件,可以始终使用原始ed而不是其流式继承器sed

    1
    2
    3
    ed"$FILE" <<<$'1d
    wq
    '

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

    序列<<<$'1d wq '利用Bash对here-strings(<<<)和POSIX引号($' ... ')的支持,将输入提供给由两行组成的ed命令:1d ,删除第1行,然后删除wq,将文件写回磁盘,然后退出编辑会话。


    sponge util避免了处理临时文件的需要:

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


    可以使用vim执行此操作:

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

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


    用csplit怎么样?

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


    您可以轻松地执行此操作:

    1
    cat filename | sed 1d > filename_without_first_line

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

    1
    sed -i 1d <filename>


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

    1
    cat textfile.txt | tail -n +2


    由于听起来我无法加速删除,我认为一个好方法可能是批量处理文件,如下所示:

    1
    2
    3
    4
    5
    While file1 not empty
      file2 = head -n1000 file1
      process file2
      sed -i -e"1000d" file1
    end

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

    (file1包含sql代码行)


    如果你想要做的是在失败后恢复,你可以建立一个具有你迄今为止所做的事情的文件。

    1
    2
    3
    4
    5
    6
    7
    8
    if [[ -f $tmpf ]] ; then
        rm -f $tmpf
    fi
    cat $srcf |
        while read line ; do
            # process line
            echo"$line">> $tmpf
        done

    会在N-1行上使用tail并将其导入文件,然后删除旧文件,并将新文件重命名为旧名称吗?

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