【问题标题】:Sort chunks of rows sequentially of multiple input files using a pattern match使用模式匹配按顺序对多个输入文件的行块进行排序
【发布时间】:2018-08-28 17:34:09
【问题描述】:

我有散布在 100 个文件中的数据块,这些文件在重新排序时会按照数字顺序排列。例如,如果我有 100 个数据块,那么块 #1、3、5 可能在一个文件中,而块 #2、4、6 可能在另一个文件中。我需要按顺序创建 1 个包含所有块的输出文件:#1,2,3,4,5,6。

以下是 2 个(共 100 个)输入文件的简化版本。每个块都以“ITEM: TIMESTEP”开头,并且需要按以下行中的数字进行组织(这里是 1000、2000、3000、4000)。

输入文件 1

项目:TIMETEP
1000
项目:原子数
50 2 H 0.4 0.3 0.006
10214 2 H 0.5 0.4 0.002
......#12,000 行之后#...
ITEM: TIMETEP
3000
项目:原子数
50 2 H 2.3 1.4 0.3
10214 2 H 2.5 1.3 0.6
......#12,000 行之后#...

输入文件 2

项目:TIMETEP
2000
项目:原子数
50 2 H 0.4 0.3 0.006
10214 2 H 0.5 0.4 0.002
......#12,000 行之后#...
ITEM: TIMETEP
4000
项目:原子数
50 2 H 2.3 1.4 0.3
10214 2 H 2.5 1.3 0.6
......#12,000 行之后#...

最终的输出文件如下所示

项目:TIMETEP
1000
....#其余部分#...
项目:TIMETEP
2000
....#其余部分#...
项目:TIMETEP
3000
....#其余部分#...
项目:TIMETEP
4000
....#rest of chunk#...

到目前为止,我已经在每个块的开头插入了一个名为“IDENTIFIER”的标识符字符串:

awk -v n=12,000 '1; NR%n==0 {print "IDENTIFIER"}' in.txt >> out1.txt

我可以打印每个标识符字符串后面的每个块所需的 N 行,循环遍历多个文件

for i in $(seq 1000 1000 10000); do
  awk 'c&&c--;/IDENTIFIER/{c=12,000}' out${i}.txt >> out-final.txt
done

我使用这种方法来专门识别每个块的第二行,因为这些数字可以在块本身内重复。但是,我不知道如何修改第二个命令行,以便它仅在 IDENTIFIER 之后的值是序列中的下一个数字时打印到 out-final.txt。

【问题讨论】:

    标签: for-loop awk pattern-matching


    【解决方案1】:

    我建议另一种方法,首先拆分文件,以便每个项目都在自己的文件中,然后按所需顺序合并文件。例如对于给定的两个文件

    $ awk '/^ITEM: TIMETEP/{h=$0; next} 
                         h {f="item_"$0; print h > f; h=""} 
                           {print > f}' file1 file2 
    

    将创建四个提取,可以简单地合并回来

    $ cat item_{1..4}000 > merged_items
    

    【讨论】:

    • 可能在重新定义f之前添加close(f)
    【解决方案2】:

    我会为此使用 perl

    cat file{1,2} | perl -0777 -ne '
        @records = split /^(?=ITEM: TIMETEP)/m;
        print join "",
            map  { $_->[1] }
            sort { $a->[0] <=> $b->[0] }
            map  { ($n) = /\n(\d+)\n/; [$n, $_] }
            @records;
    '
    

    -0777 选项强制 perl 将整个输入转换为单个字符串。我们使用标头拆分为记录。然后进行 Schwartzian 变换进行排序,并再次将记录连接在一起并打印。


    如果你喜欢痛苦,这里是线路嘈杂的单行版本:

    cat file{1,2} | perl -0777 -pe'$_=join"",map{$_->[1]}sort{$a->[0]<=>$b->[0]}map{[/\n(\d+)\n/,$_]}split/^(?=ITEM: TIMETEP)/m'
    

    【讨论】:

      【解决方案3】:

      在每条记录前面加上每条记录的第 2 行的记录 ID 和自该记录开始以来的行号,按该记录 ID 和行号排序,然后在排序后再次删除它们:

      $ cat tst.sh
      awk '
          BEGIN { OFS="\t" }
          /^ITEM: TIMETEP/ { head=$0; lineNr=1; next }
          lineNr == 1 { recId=$0; print recId, lineNr, head }
          { print recId, ++lineNr, $0 }
      ' "$@" |
      sort -k1,2n |
      cut -f3-
      
      $ ./tst.sh file1 file2
      ITEM: TIMETEP
      1000
      ITEM: NUMBER OF ATOMS
      50 2 H 0.4 0.3 0.006
      10214 2 H 0.5 0.4 0.002
      ......#12,000 lines later#...
      ITEM: TIMETEP
      2000
      ITEM: NUMBER OF ATOMS
      50 2 H 0.4 0.3 0.006
      10214 2 H 0.5 0.4 0.002
      ......#12,000 lines later#...
      ITEM: TIMETEP
      3000
      ITEM: NUMBER OF ATOMS
      50 2 H 2.3 1.4 0.3
      10214 2 H 2.5 1.3 0.6
      ......#12,000 lines later#...
      ITEM: TIMETEP
      4000
      ITEM: NUMBER OF ATOMS
      50 2 H 2.3 1.4 0.3
      10214 2 H 2.5 1.3 0.6
      ......#12,000 lines later#...
      

      由于上面唯一“一次”(而不是逐行)处理所有输入的命令是sort,它适用于大量大文件,因为sort 旨在进行分页,等来处理大量输入(请参阅https://unix.stackexchange.com/a/279099/133219)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-05-29
        • 2019-12-29
        • 2010-12-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-25
        相关资源
        最近更新 更多