【问题标题】:Bash script to print X lines of a file in sequence用于按顺序打印文件的 X 行的 Bash 脚本
【发布时间】:2018-09-28 14:54:53
【问题描述】:

非常感谢您对一些可能非常简单的事情的帮助。

我有一个表 (table2.txt),其中有一列随机生成的数字,大约有一百万行长。

2655087
3721239
5728533
9082076
2016819
8983893
9446748
6607974

我想创建一个重复 10,000 次的循环,因此对于迭代 1,我将第 1 到 4 行打印到文件 (file0.txt),对于迭代 2,我打印第 5 到 8 行 (file1.txt) , 等等。

到目前为止,我所拥有的是:

#!/bin/bash
for i in {0..10000}
do
awk 'NR==((4 * "$i") +1)' table2.txt > file"$i".txt
awk 'NR==((4 * "$i") +2)' table2.txt >> file"$i".txt
awk 'NR==((4 * "$i") +3)' table2.txt >> file"$i".txt
awk 'NR==((4 * "$i") +4)' table2.txt >> file"$i".txt
done

file0.txt 的所需输出:

2655087
3721239
5728533
9082076

file1.txt 的所需输出:

2016819
8983893
9446748
6607974

这出了点问题,因为我从所有文件中获得了相同的输出(即它们看起来都像 file0.txt 的所需输出)。希望您可以从我的脚本中看到,在第二次迭代期间,即当 i=2 时,我希望输出为第 5、6、7 和 8 行的值。

这可能是一个非常简单的语法错误,如果您能告诉我哪里出错了(或者给我一个不那么麻烦的解决方案!),我将不胜感激!

非常感谢。

【问题讨论】:

    标签: bash loops awk


    【解决方案1】:

    awk 的美妙之处在于您可以在一条awk 行中完成此操作:

    awk '{ print > ("file"c".txt") }
         (NR % 4 == 0) { ++c }
         (c == 10001) { exit }' <file>
    

    这可以稍微优化一些并且文件处理更友好(cfr.James Brown):

    awk 'BEGIN{f="file0.txt" }
         { print > f }
         (NR % 4 == 0) { close(f); f="file"++c".txt" }
         (c == 10001) { exit }' <file>
    

    为什么你的脚本失败了?

    您的脚本失败的原因是您使用了单引号并试图将 shell 变量传递给它。你的台词应该是:

    awk 'NR==((4 * '$i') +1)' table2.txt > file"$i".txt
    

    但这很丑陋,应该用

    来改进
    awk -v i=$i 'NR==(4*i+1)' table2.txt > file"$i".txt
    

    为什么你的脚本很慢?

    您处理文件的方式是循环进行 10001 次迭代。每次迭代,您执行 4 次 awk 调用。每个awk 调用都会完全读取整个文件并写出一行。所以最后你读了你的文件 40004 次。

    要逐步优化您的脚本,我会执行以下操作:

    1. 终止awk以在打印行后逐步读取文件

      #!/bin/bash
      for i in {0..10000}; do
        awk -v i=$i 'NR==(4*i+1){print; exit}' table2.txt > file"$i".txt
        awk -v i=$i 'NR==(4*i+2){print; exit}' table2.txt >> file"$i".txt
        awk -v i=$i 'NR==(4*i+3){print; exit}' table2.txt >> file"$i".txt
        awk -v i=$i 'NR==(4*i+4){print; exit}' table2.txt >> file"$i".txt
      done
      
    2. 将 4 个 awk 调用合并为一个。这可以防止在每个循环周期中一遍又一遍地读取第一行。

      #!/bin/bash
      for i in {0..10000}; do
        awk -v i=$i '(NR<=4*i)    {next}            # skip line
                     (NR> 4*(i+1)}{exit}            # exit awk
                     1' table2.txt > file"$i".txt  # print line
      done
      
    3. 删除最后一个循环(请参阅此答案的顶部)

    【讨论】:

    • 谢谢!那是非常优雅的!但是有两个问题:1. 是否可以让文件输出为“file1.txt”、“file2.txt”,而不是“file00001.txt”、“file00002.txt”? 2. 其次,这会在 10,000 次迭代时自动停止吗?再次感谢。
    【解决方案2】:

    这在功能上与@JamesBrown's answer 相同,但只是写得更 awk-ishly 所以不要接受这个,我只是发布它以显示更惯用的 awk 语法,因为你不能将格式化的代码放在评论中。

    awk '
        (NR%4)==1 { close(out); out="file" c++ ".txt" }
        c > 10000 { exit }
        { print > out }
    ' file
    

    请参阅why-is-using-a-shell-loop-to-process-text-considered-bad-practice,了解为什么应避免使用 shell 循环来操作文本的一些原因。

    【讨论】:

      【解决方案3】:

      只需 bash,您就可以非常简单地做到这一点:

      chunk=4
      files=10000
      head -n $(($chunk*$files)) table2.txt |
        split -d -a 5 --additional-suffix=.txt -l $chunk - file
      

      基本上读取前 10k 行并将它们分成 4 个连续行的块,使用 file 作为前缀,.txt 作为新文件的后缀。

      如果您想要一个数字标识符,则需要 5 位数字 (-a 5),正如 cmets 中所指出的那样(来源:@kvantour)。

      【讨论】:

      • 注意它是head -n 40004 table2.txt
      • 除此之外,您还需要添加选项 -a 5 以允许 10000 个文件。
      • 已编辑。谢谢!
      • 数学部分不应该是$(($chunk*$files))而不是$($chunk*$files)吗?在管道 (|) 之后的行尾也不需要反斜杠。我很佩服你说它可以“非常简单”的热情:-)。
      • 我同意@EdMorton 的选项使它有点复杂。简单地说,我的意思是它根本不需要带有模运算的循环,只需要两个用管道连接的命令(在 bash 中很常见)。我已经更正了$(( ))。反斜杠是为了避免代码块插入滚动条。
      【解决方案4】:

      另一个 awk:

      $ awk '{if(NR%4==1){if(i==10000)exit;close(f);f="file" i++ ".txt"}print > f}' file
      $ ls 
      file file0.txt  file1.txt
      

      解释:

      awk ' {
          if(NR%4==1) {            # use mod to recognize first record of group
              if(i==10000)         # exit after 10000 files 
                  exit             # test with 1
              close(f)             # close previous file
              f="file" i++ ".txt"  # make a new filename
          }
          print > f                # output record to file
      }' file
      

      【讨论】:

        猜你喜欢
        • 2015-01-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-18
        • 2011-06-28
        • 1970-01-01
        • 2012-09-28
        • 1970-01-01
        相关资源
        最近更新 更多