【问题标题】:How can I extract a predetermined range of lines from a text file on Unix?如何从 Unix 上的文本文件中提取预定范围的行?
【发布时间】:2010-09-10 03:29:46
【问题描述】:

我有一个约 23000 行的 SQL 转储,其中包含多个数据库的数据。我需要提取该文件的某个部分(即单个数据库的数据)并将其放在一个新文件中。我知道我想要的数据的开始行号和结束行号。

有谁知道一个 Unix 命令(或一系列命令)从文件中提取所有行,比如第 16224 行和第 16482 行之间的行,然后将它们重定向到一个新文件中?

【问题讨论】:

标签: unix command-line sed text-processing


【解决方案1】:

您可以使用“vi”,然后使用以下命令:

:16224,16482w!/tmp/some-file

或者:

cat file | head -n 16482 | tail -n 258

编辑:- 只是为了添加解释,您使用 head -n 16482 来显示前 16482 行,然后使用 tail -n 258 来获取最后 258 行第一个输出。

【讨论】:

  • 你可以用 ex 代替 vi,也就是 vi 减去交互式控制台的东西。
  • 您不需要cat 命令; head 可以直接读取文件。这比许多替代方案要慢,因为它使用 2 个(如图所示 3 个)命令,其中 1 个就足够了。
  • @JonathanLeffler 你错了。它的速度非常快。我在几秒钟内从一个有 500k 行的 2G 文件中提取了 200k 行,大约 1G(没有cat)。其他解决方案至少需要几分钟。此外,GNU 上最快的变体似乎是tail -n +XXX filename | head XXX
【解决方案2】:
sed -n '16224,16482p;16483q' filename > newfile

来自sed manual

p - 打印出模式空间(到标准输出)。该命令通常只与 -n 命令行选项一起使用。

n - 如果未禁用自动打印,则打印模式空间,然后无论如何,用下一行输入替换模式空间。如果 没有更多的输入然后 sed 退出而不进行任何处理 命令。

q - 退出sed,不再处理任何命令或输入。 请注意,如果未使用 -n 选项禁用自动打印,则会打印当前模式空间。

and

sed 脚本中的地址可以是以下任何一种形式:

号码 指定行号将仅匹配输入中的该行。

一个地址范围可以通过指定两个地址来指定 用逗号 (,) 分隔。地址范围匹配从以下开始的行 第一个地址匹配的地方,一直持续到第二个 地址匹配(包括)。

【讨论】:

  • 我很好奇这是否会修改原始文件。我备份了它以防万一,看来这并没有像预期的那样修改原始文件。
  • @AndyGroff。要就地修改文件,请使用“-i”参数。否则不会修改文件。
  • 如果像我一样,您需要在一个非常大的文件上执行此操作,如果您在下一行添加退出命令会有所帮助。然后是sed -n '16224,16482p;16483q' filename。否则 sed 会一直扫描到最后(或者至少我的版本会这样)。
  • @MilesRout 人们似乎在问“为什么要投反对票?”很多时候,也许你的意思是“我不在乎”而不是“没人在乎”
  • @wds - 您的评论理应得到一个登上榜首的答案。它可以区分白天和黑夜。
【解决方案3】:
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2

【讨论】:

    【解决方案4】:
    perl -ne 'print if 16224..16482' file.txt > new_file.txt
    

    【讨论】:

    • @JonathanLeffler 在您的几个回复中,您写道“这比许多替代方案慢,因为它使用 2 个(如图所示的 3 个)命令,其中 1 个就足够了。”请问上面的perl oneliner 是不是你所说的“1 就足够了”和/或你的意思是sed 还是awk oneliner?哪个最快?
    • 我的cmets主要参考UUoC — Useless Use of cat。使用cat file | something …,其中something 可以直接从文件中读取,应该总是比让something 直接从文件中读取要慢,因为cat 命令必须读取文件并将其写入管道,而something必须读取管道的内容并进行处理。这意味着对文件中数据的复制比必要的要多。这就是我断言的基础。我没有进行正式的测试,但要避免减速需要一些奇怪的东西。
    • 那个“奇怪的东西”可能是splice(2)(另见splice(2))。 OTOH,我不知道 Linux 上的 cat 是否使用它——你必须研究 cat 的源代码。
    • 我注意到我的解决方案容易受到“但它读取整个文件”问题的影响。 Perl 可能会优化代码,但您必须研究生成的字节码才能知道它是否优化。但这绝对避免了 UUoC 问题。请注意,并非所有使用前导 cat 都是错误的,但使用单个文件名参数通常是错误的。使用cat "$@" | something … 可以处理0、1 或多个命令行参数,并将something 的输入作为单个文件进行本地化——这很重要。但是something "$@" 可能同样有效(除非something 拼写为tr 等)。
    【解决方案5】:

    又快又脏:

    head -16428 < file.in | tail -259 > file.out
    

    可能不是最好的方法,但应该可以。

    顺便说一句:259 = 16482-16224+1。

    【讨论】:

    • 这比许多替代方案慢,因为它使用 2 个命令,其中 1 个就足够了。
    【解决方案6】:

    sed -n '16224,16482p' &lt; dump.sql

    【讨论】:

      【解决方案7】:

      使用头/尾非常简单:

      head -16482 in.sql | tail -258 > out.sql
      

      使用 sed:

      sed -n '16224,16482p' in.sql > out.sql
      

      使用 awk:

      awk 'NR>=16224&&NR<=16482' in.sql > out.sql
      

      【讨论】:

      • 第二个和第三个选项都可以,但第一个比许多替代方案慢,因为它使用 2 个命令,其中 1 就足够了。它还需要计算才能为tail 提供正确的参数。
      • 值得注意的是,要保持与问题相同的行号,sed 命令应为sed -n 16224,16482p' in.sql &gt;out.sql,awk 命令应为awk 'NR&gt;=16224&amp;&amp;NR&lt;=16482' in.sql &gt; out.sql
      • 另外值得知道的是,在第一个示例的情况下,head -16482 in.sql | tail -$((16482-16224)) &gt;out.sql 将计算留给 bash
      • 第一个带有头部和尾部的 WAYYYY 在大文件上比 sed 版本更快,即使添加了 q-option。 head-version Instant 和 sed 版本我 Ctrl-C 一分钟后...谢谢
      • 也可以使用tail -n +16224来减少计算量
      【解决方案8】:
      sed -n '16224,16482 p' orig-data-file > new-file
      

      其中 16224,16482 是起始行号和结束行号,包括在内。这是 1 索引的。 -n 禁止将输入回显为输出,这显然是您不想要的;数字表示执行以下命令的行范围;命令p 打印出相关行。

      【讨论】:

      • 在大文件上,上述命令将在找到所需范围后继续遍历整个文件。一旦输出范围,有没有办法让 sed 停止处理文件?
      • 好吧,从the answer here 看来,在范围末尾停止似乎可以通过:sed -n '16224,16482p;16482q' orig-data-file &gt; new-file 来完成。
      • 你为什么要放一个不必要的空间,然后必须引用? (当然,制造不必要的问题并解决它们是一半计算机科学的本质,但我的意思是除此之外......)
      【解决方案9】:
      cat dump.txt | head -16224 | tail -258
      

      应该可以解决问题。这种方法的缺点是您需要进行算术运算来确定 tail 的参数,并说明您是否希望 'between' 包含结束行。

      【讨论】:

      • 您不需要cat 命令; head 可以直接读取文件。这比许多替代方案要慢,因为它使用 2 个(如图所示 3 个)命令,其中 1 个就足够了。
      • @JonathanLeffler 这个答案是最容易阅读和记住的。如果您真的关心性能,那么您一开始就不会使用 shell。让特定工具专注于某项任务是一种很好的做法。此外,“算术”可以使用| tail -$((16482 - 16224)) 解决。
      【解决方案10】:

      我正要发布头/尾技巧,但实际上我可能只是启动了 emacs。 ;-)

      1. esc-x goto-line ret 16224
      2. 标记(ctrl-空格
      3. esc-x goto-line ret 16482
      4. esc-w

      打开新的输出文件,ctl-y 保存

      让我看看发生了什么。

      【讨论】:

      • 根据我的经验,Emacs 在处理非常大的文件时表现不佳。
      • 您可以将其作为脚本操作运行,还是仅作为交互式选项运行?
      【解决方案11】:

      我会使用:

      awk 'FNR >= 16224 && FNR <= 16482' my_file > extracted.txt
      

      FNR 包含正在从文件中读取的行的记录(行)号。

      【讨论】:

        【解决方案12】:

        我编写了一个名为 splitter 的 Haskell 程序,它正是这样做的:有一个 read through my release blog post

        您可以按如下方式使用该程序:

        $ cat somefile | splitter 16224-16482
        

        这就是它的全部。您将需要 Haskell 来安装它。只是:

        $ cabal install splitter
        

        你就完成了。我希望你觉得这个程序很有用。

        【讨论】:

        • splitter 是否只从标准输入读取?从某种意义上说,没关系; cat 命令不管有没有都是多余的。使用splitter 16224-16482 &lt; somefile 或(如果它采用文件名参数)splitter 16224-16482 somefile
        【解决方案13】:

        awk 还有另一种方法:

        awk 'NR==16224, NR==16482' file
        

        如果文件很大,可以在读取最后一个所需行后exit。这样,它就不会不必要地读取以下行:

        awk 'NR==16224, NR==16482-1; NR==16482 {print; exit}' file
        
        awk 'NR==16224, NR==16482; NR==16482 {exit}' file
        

        【讨论】:

        • 1+ 用于使用print; exit 节省运行时间和资源。谢谢!
        • 第二个例子的轻微简化:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
        • 太好了,谢谢@RobinA.Meade!我在帖子中编辑了你的想法
        【解决方案14】:

        我们甚至可以通过命令行来检查:

        cat filename|sed 'n1,n2!d' > abc.txt
        

        例如:

        cat foo.pl|sed '100,200!d' > abc.txt
        

        【讨论】:

        • 您不需要在其中任何一个中使用cat 命令; sed 完全能够自行读取文件,或者您可以从文件重定向标准输入。
        【解决方案15】:

        我编写了一个小 bash 脚本,您可以从命令行运行它,只要您更新 PATH 以包含其目录(或者您可以将其放置在 PATH 中已包含的目录中)。

        用法:$ pinch filename start-line end-line

        #!/bin/bash
        # Display line number ranges of a file to the terminal.
        # Usage: $ pinch filename start-line end-line
        # By Evan J. Coon
        
        FILENAME=$1
        START=$2
        END=$3
        
        ERROR="[PINCH ERROR]"
        
        # Check that the number of arguments is 3
        if [ $# -lt 3 ]; then
            echo "$ERROR Need three arguments: Filename Start-line End-line"
            exit 1
        fi
        
        # Check that the file exists.
        if [ ! -f "$FILENAME" ]; then
            echo -e "$ERROR File does not exist. \n\t$FILENAME"
            exit 1
        fi
        
        # Check that start-line is not greater than end-line
        if [ "$START" -gt "$END" ]; then
            echo -e "$ERROR Start line is greater than End line."
            exit 1
        fi
        
        # Check that start-line is positive.
        if [ "$START" -lt 0 ]; then
            echo -e "$ERROR Start line is less than 0."
            exit 1
        fi
        
        # Check that end-line is positive.
        if [ "$END" -lt 0 ]; then
            echo -e "$ERROR End line is less than 0."
            exit 1
        fi
        
        NUMOFLINES=$(wc -l < "$FILENAME")
        
        # Check that end-line is not greater than the number of lines in the file.
        if [ "$END" -gt "$NUMOFLINES" ]; then
            echo -e "$ERROR End line is greater than number of lines in file."
            exit 1
        fi
        
        # The distance from the end of the file to end-line
        ENDDIFF=$(( NUMOFLINES - END ))
        
        # For larger files, this will run more quickly. If the distance from the
        # end of the file to the end-line is less than the distance from the
        # start of the file to the start-line, then start pinching from the
        # bottom as opposed to the top.
        if [ "$START" -lt "$ENDDIFF" ]; then
            < "$FILENAME" head -n $END | tail -n +$START
        else
            < "$FILENAME" tail -n +$START | head -n $(( END-START+1 ))
        fi
        
        # Success
        exit 0
        

        【讨论】:

        • 这比许多替代方案慢,因为它使用 2 个命令,其中 1 个就足够了。事实上,它读取文件两次是因为wc 命令,这会浪费磁盘带宽,尤其是在千兆字节文件上。从各方面来说,这都是有据可查的,但这也是工程上的矫枉过正。
        • 喜欢用“捏”这个词来描述它的作用。 :D
        【解决方案16】:

        使用红宝石:

        ruby -ne 'puts "#{$.}: #{$_}" if $. >= 32613500 && $. <= 32614500' < GND.rdf > GND.extract.rdf
        

        【讨论】:

          【解决方案17】:

          这可能对你有用(GNU sed):

          sed -ne '16224,16482w newfile' -e '16482q' file
          

          或利用 bash:

          sed -n $'16224,16482w newfile\n16482q' file
          

          【讨论】:

            【解决方案18】:

            接受答案中的 -n 有效。如果您愿意,这里还有另一种方法。

            cat $filename | sed "${linenum}p;d";
            

            执行以下操作:

            1. 管道输入文件内容(或根据需要输入文本)。
            2. sed 选择给定的行,打印出来
            3. d 是删除行所必需的,否则 sed 将假定最终将打印所有行。即,如果没有 d,您将打印两次所选行打印的所有行,因为您有 ${linenum}p 部分要求打印它。我很确定 -n 基本上和这里的 d 做同样的事情。

            【讨论】:

            • 注意cat file | sed 最好写成sed file
            • 这只是打印一行,而问题是关于它们的范围。
            【解决方案19】:

            我想从一个使用变量的脚本中做同样的事情,并通过在 $variable 周围加上引号来将变量名与 p 分开来实现它:

            sed -n "$first","$count"p imagelist.txt >"$imageblock"
            

            我想将一个列表拆分为单独的文件夹,并发现最初的问题和回答是一个有用的步骤。 (拆分命令不是我必须将代码移植到的旧操作系统的选项)。

            【讨论】:

              【解决方案20】:

              由于我们正在讨论从文本文件中提取文本行,因此我将给出一个特殊情况,您希望提取与特定模式匹配的所有行。

              myfile content:
              =====================
              line1 not needed
              line2 also discarded
              [Data]
              first data line
              second data line
              =====================
              sed -n '/Data/,$p' myfile
              

              将打印 [Data] 行和剩余的行。如果您想要从 line1 到模式的文本,请键入:sed -n '1,/Data/p' myfile。此外,如果您知道两种模式(最好在您的文本中是唯一的),则范围的开始行和结束行都可以使用匹配项来指定。

              sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
              

              【讨论】:

                【解决方案21】:

                站在boxxar的肩膀上,我喜欢这样:

                sed -n '<first line>,$p;<last line>q' input
                

                例如

                sed -n '16224,$p;16482q' input
                

                $ 表示“最后一行”,所以第一个命令使 sed 打印所有以行 16224 开头的行,第二个命令使 sed 退出 after 打印行 @ 987654327@。 (在boxxar的解决方案中为q-range添加1似乎没有必要。)

                我喜欢这个变体,因为我不需要指定结束行号两次。而且我测量到使用$ 不会对性能产生不利影响。

                【讨论】:

                  【解决方案22】:

                  使用 ed:

                  ed -s infile <<<'16224,16482p'
                  

                  -s 抑制诊断输出;实际命令位于此处的字符串中。具体来说,16224,16482p 在所需的行地址范围内运行p(打印)命令。

                  【讨论】:

                    【解决方案23】:

                    只是对上面给出的 3 个解决方案进行基准测试,这对我有用:

                    • awk
                    • sed
                    • “头+尾”

                    这 3 个解决方案的功劳归于:

                    • @boxxar
                    • @avandeursen
                    • @wds
                    • @manveru
                    • @sibaz
                    • @SOFe
                    • @fedorqui '停止伤害'
                    • @Robin A.米德

                    我正在使用我在服务器中找到的一个大文件:

                    # wc fo2debug.1.log
                       10421186    19448208 38795491134 fo2debug.1.log
                    

                    38 Gb,1040 万行。

                    是的,我有一个 logrotate 问题。 :))


                    下注!


                    从文件开头获取 256 行。

                    # time sed -n '1001,1256p;1256q' fo2debug.1.log | wc -l
                    256
                    
                    real    0m0,003s
                    user    0m0,000s
                    sys     0m0,004s
                    
                    # time head -1256 fo2debug.1.log | tail -n +1001 | wc -l
                    256
                    
                    real    0m0,003s
                    user    0m0,006s
                    sys     0m0,000s
                    
                    # time awk 'NR==1001, NR==1256; NR==1256 {exit}' fo2debug.1.log | wc -l
                    256
                    
                    real    0m0,002s
                    user    0m0,004s
                    sys     0m0,000s
                    

                    Awk 赢了。 sed 和“head+tail”在技术上并列第二。


                    在文件前三分之一的末尾获取 256 行。

                    # time sed -n '3473001,3473256p;3473256q' fo2debug.1.log | wc -l
                    256
                    
                    real    0m0,265s
                    user    0m0,242s
                    sys     0m0,024s
                    
                    # time head -3473256 fo2debug.1.log | tail -n +3473001 | wc -l
                    256
                    
                    real    0m0,308s
                    user    0m0,313s
                    sys     0m0,145s
                    
                    # time awk 'NR==3473001, NR==3473256; NR==3473256 {exit}' fo2debug.1.log | wc -l
                    256
                    
                    real    0m0,393s
                    user    0m0,326s
                    sys     0m0,068s
                    

                    Sed 赢了。接着是“head+tail”,最后是 awk。


                    在文件的第二个三分之一的末尾获取 256 行。

                    # time sed -n '6947001,6947256p;6947256q' fo2debug.1.log | wc -l
                    A256
                    
                    real    0m0,525s
                    user    0m0,462s
                    sys     0m0,064s
                    
                    # time head -6947256 fo2debug.1.log | tail -n +6947001 | wc -l
                    256
                    
                    real    0m0,615s
                    user    0m0,488s
                    sys     0m0,423s
                    
                    # time awk 'NR==6947001, NR==6947256; NR==6947256 {exit}' fo2debug.1.log | wc -l
                    256
                    
                    real    0m0,779s
                    user    0m0,650s
                    sys     0m0,130s
                    

                    同样的结果。

                    Sed 赢了。接着是“head+tail”,最后是 awk。


                    在文件末尾附近获得 256 行。

                    # time sed -n '10420001,10420256p;10420256q' fo2debug.1.log | wc -l
                    256
                    
                    real    1m50,017s
                    user    0m12,735s
                    sys     0m22,926s
                    
                    # time head -10420256 fo2debug.1.log | tail -n +10420001 | wc -l
                    256
                    
                    real    1m48,269s
                    user    0m42,404s
                    sys     0m51,015s
                    
                    # time awk 'NR==10420001, NR==10420256; NR==10420256 {exit}' fo2debug.1.log | wc -l
                    256
                    
                    real    1m49,106s
                    user    0m12,322s
                    sys     0m18,576s
                    

                    突然,一个转折!

                    “头+尾”赢了。接着是 awk,最后是 sed。


                    (几个小时后...)

                    对不起各位!

                    我上面的分析最终成为分析中存在基本缺陷的一个例子。

                    缺陷在于不深入了解用于分析的资源。

                    在这种情况下,我使用了一个日志文件来分析在其中搜索一定数量的行的性能。

                    使用 3 种不同的技术,在文件的不同点进行搜索,比较技术在每个点的性能,并检查结果是否因文件中进行搜索的点而异。

                    我的错误是假设日志文件中的内容具有一定的同质性。

                    现实情况是,长行出现在文件末尾的频率更高。

                    因此,使用给定技术进行较长时间搜索(更接近文件末尾)更好的明显结论可能是有偏见的。事实上,这种技术在处理较长的行时可能会更好。还有待确认。

                    【讨论】:

                      【解决方案24】:

                      我一直在寻找这个问题的答案,但我最终不得不编写自己的代码,该代码有效。上面的答案没有一个是令人满意的。 考虑您有非常大的文件并且有某些要打印的行号,但这些数字不按顺序排列。您可以执行以下操作:

                      我的文件比较大 for letter in {a..k} ; do echo $letter; done | cat -n &gt; myfile.txt

                       1  a
                       2  b
                       3  c
                       4  d
                       5  e
                       6  f
                       7  g
                       8  h
                       9  i
                      10  j
                      11  k
                      

                      我想要的特定行号: shuf -i 1-11 -n 4 &gt; line_numbers_I_want.txt

                       10
                       11
                       4
                       9
                      

                      要打印这些行号,请执行以下操作。 awk '{system("head myfile.txt -n " $0 " | tail -n 1")}' line_numbers_I_want.txt

                      上面所做的就是将第 n 行放在前面,然后使用 tail 取最后一行

                      如果您希望按顺序排列行号,请先排序(is -n 数字排序)然后获取行数。

                      cat line_numbers_I_want.txt | sort -n | awk '{system("head myfile.txt -n " $0 " | tail -n 1")}'

                       4  d
                       9  i
                      10  j
                      11  k
                      

                      【讨论】:

                        【解决方案25】:

                        试图为head | tail 组合计算间隔的人想多了。

                        以下是在不计算任何内容的情况下获得“16224 -- 16482”范围的方法:

                        cat file | head -n +16482 | tail -n +16224
                        

                        解释:

                        • + 指示head/tail 命令“上升到 / 开始”(分别)指定的行号从文件开头算起

                        • 同样,- 指示他们“上升到 / 开始”(分别)指定的行号从末尾开始计数文件

                        • 上面显示的解决方案首先简单地使用head,以“将所有内容保留在顶部数字”,然后使用tail,以“将所有内容从底部数字保留”向上',从而定义我们感兴趣的范围(无需计算区间)。

                        【讨论】:

                        • 我发现名字比数字更容易理解;我把它写成head -n +"$last_line" "$full_log_file" | tail -n +"$first_line" &gt; "$cropped_log_file"
                        猜你喜欢
                        • 2021-11-23
                        • 1970-01-01
                        • 2023-03-25
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2019-07-23
                        • 2013-06-25
                        • 2017-02-13
                        相关资源
                        最近更新 更多