Bash脚本按顺序打印文件的X行

时间:2018-04-18 11:20:55

标签: bash loops awk

我非常感谢你的帮助,这可能很简单。

我有一个表(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行的值。

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

非常感谢你。

4 个答案:

答案 0 :(得分:6)

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 :(得分: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),如评论中所指出的那样(信用:@kvantour)。

答案 2 :(得分:3)

这在功能上与@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 :(得分:2)

另一个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