【问题标题】:Splitting a large file with awk into chunks with a defined number of multi line records使用 awk 将大文件拆分为具有定义数量的多行记录的块
【发布时间】:2018-09-14 22:57:24
【问题描述】:

我想将一个大文件(>15G,几百万条记录)分块为具有定义数量的记录的较小块。我正在使用 Ubuntu 16.04。

规则如下:

  1. 对于可移植性问题,我想坚持使用 UNIX 命令。
  2. 有一个特定的模式定义输入文件中每条记录的结尾 ('$$$$')。
  3. 应保留此模式以将记录分隔成块
  4. 每个块应该包含 n 条记录
  5. 每条记录的行数都可以不同。

我搜索了类似的问题like this one,但找不到我要找的确切内容。

这是输入文件语法的示例。

example.sdf

Item1
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.7946    2.9241    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.9708    2.9673    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
3

$$$$
Element2
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.6161    1.7634    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.7956    1.8496    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
5

$$$$
Something3
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.0580    0.5134    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -3.5772    1.1545    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
10

$$$$

n=2 的期望输出:

example.sdf.chunk000001

Item1
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.7946    2.9241    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.9708    2.9673    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
3

$$$$
Element2
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.6161    1.7634    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.7956    1.8496    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
5

$$$$

example.sdf.chunk000002

Something3
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.0580    0.5134    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -3.5772    1.1545    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
10

$$$$

目前,我尝试使用 split 和 awk 来实现这一点(见下文),但这看起来很笨拙。我还尝试查看 csplit,但我找不到任何选项来设置每个块中定义的记录数。

拆分

split 命令可以正常工作,但不接受 '$$$$' 分隔符,因为它不止一个字符。我可以通过用单个字符 (@) 替换此模式来使其工作,但如果在 SDF 文件中找到此其他字符,则可能会出错。

# replace the separator with a dummy
sed -e 's/\$\$\$\$/@/g' export.sdf > example.sdf.tmp
# split the file (3 records) into smaller chunks (xaa, xab, ect.) with max 2 records
split -t @ -l 2 example.sdf.tmp
# replace the dummy with the proper separator
for f in xa*; do tail -n +2 $f |sed 's/@/\$\$\$\$/g' > $f.fixed; done

不幸的是,这对于编辑输入文件和每个块来说看起来并不是很优化,所以我尝试使用 awk 来代替。

awk

我对 awk 很陌生,但我设法得到了这个:

awk 'NR%2==1 {x=sprintf(".chunk%06d",++i);} END {printf "%s",$0} {print>FILENAME x}' RS="\\$\\$\\$\\$" ORS="\$\$\$\$" example.sdf

第一个块看起来正是我要找的,但第二个有两个错误:

example.sdf.chunk000002

[ blank line ]     
Something3
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.0580    0.5134    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -3.5772    1.1545    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
10

$$$$
$$$$

如您所见,文件开头有一个空行(我无法显示,所以我输入了 [blank line]),最后一个块的末尾有一个最终结束模式。我还尝试了一个包含 9 条记录的文件,我在块 2-5 的开头得到了空行,在块 5 的末尾得到了最后一个额外的 '$$$$'。

如何解决此问题,以便获得预期的输出?

任何帮助将不胜感激!

何塞·曼努埃尔

【问题讨论】:

    标签: bash awk


    【解决方案1】:

    这应该有效:

    awk 'BEGIN{n_records=2; counter=0};{print > "file_" int(counter/n_records) ".txt"; if($0 ~ /\$\$\$\$/){counter++}}' example.sdf
    

    【讨论】:

    • 请注意,如果您有很多块,这可能会对大文件造成问题。建议在移动到新文件之前close 上一个文件。
    • 基本上,它只适用于 GNU awk。对于所有其他 awk,当您超过大约 20 个输出文件时它会失败,并且由于输出文件表达式没有被括号括起来,它会因为语法错误而失败。由于它需要 GNU,因此您应该使用 multi-char RS="$$$$" 而不是这种方法。所以,这不是一个好的解决方案。
    【解决方案2】:

    使用 GNU awk 进行多字符 RS、RT 和处理多个打开的文件:

    $ awk -v RS='\n[$]{4}\n' 'NR%2{out="out"++c} {print $0 RT " > " out}' file
    Item1
      Mrv171c009131823372D
    
      2  1  0  0  0  0            999 V2000
       -3.7946    2.9241    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
       -2.9708    2.9673    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
      1  2  1  0  0  0  0
    M  END
    > <property_1>
    3
    
    $$$$
     > out1
    Element2
      Mrv171c009131823372D
    
      2  1  0  0  0  0            999 V2000
       -3.6161    1.7634    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
       -2.7956    1.8496    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
      1  2  1  0  0  0  0
    M  END
    > <property_1>
    5
    
    $$$$
     > out1
    Something3
      Mrv171c009131823372D
    
      2  1  0  0  0  0            999 V2000
       -3.0580    0.5134    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
       -3.5772    1.1545    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
      1  2  1  0  0  0  0
    M  END
    > <property_1>
    10
    
    $$$$
     > out2
    

    在您测试并满意输出后,只需将 " &gt; " 更改为 &gt;

    使用任何 awk:

    awk '
        NR==1 { out="out"++c }
        { print > out }
        ($0=="$$$$") && (((++nr)%2)==0) { close(out); out="out"++c }
    ' file
    

    【讨论】:

      【解决方案3】:

      使用 GNU awk:

      awk -v RS='\\$\\$\\$\\$\n' -v nb=2 -v c=1 '
      {
         file=sprintf("%s%s%06d",FILENAME,".chunk",c)
         printf "%s%s",$0,RT > file 
      }
      NR%nb==0 {c++}
      ' example.sdk
      

      记录分隔符RS 到模式$$$$ 允许一次获取完整块。

      变量nb保存每个文件的块数,c是文件名的计数。

      【讨论】:

      • 这会删除输出文件中的“$$$$”模式,但我需要这些。我尝试通过指定 ORS='\\$\\$\\$\\$\n' 来解决此问题,但输出没有任何差异。
      • 我可以通过编辑 printf 得到我想要的: printf "%s%s$$$$\n",$0,RT > file
      • @JoseManuel 模式$$$$ 是输出文件的一部分,因为这是RT 变量的捕获。也许你没有使用 Gnu awk。
      • @JoseManuel 在printf 语句中使用RS 而不是RT
      • 你是对的,看起来在 Ubuntu 16.04 中默认使用 mawk。我试图替换 RS 而不是 RT,但在块中得到 \$\$\$\$。当然,如果我将 RS='\\$\\$\\$\\$\n' 切换为 RS='$$$$\n',我就不会再得到任何块了。如果我定义 ORS='$$$$\n' 并在 printf 语句中使用 printf ORS 而不是 RT,它将起作用。
      【解决方案4】:

      这里是对Cortenin Limier的解决方案的一个小更新

      原文:

      awk 'BEGIN{n_records=2; counter=0}
          { print > "file_" int(counter/n_records) ".txt";
            if($0 ~ /\$\$\$\$/){counter++}}' example.sdf
      

      更新:

      awk 'BEGIN{n_records=2; }
           (NR==1){ file=sprintf(FILENAME ".chunk%0.6d",counter) }
           { print > file }
           ($0=="$$$$"){ 
               close(file); 
               file=sprintf(FILENAME ".chunk%0.6d",(++counter/n_records))
           }' example.sdf
      

      区别在于:

      • 任何变量默认为零或空字符串,因此无需定义counter=0
      • 变量file保存文件名,所以不是每一步都生成的
      • file 在不再需要时关闭。
      • 我们检查记录分隔符是否实际位于行首和行尾。
      • 输出文件的格式为FILENAME.chunknnnnnn,其中FILENAME 替换为此处调用的原始文件example.sdf

      【讨论】:

      • 第一个输出 example.sdf.chunk000000 中缺少标记为“Row0”的第一条记录
      猜你喜欢
      • 2019-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-22
      • 1970-01-01
      • 2020-09-13
      • 1970-01-01
      • 2016-08-01
      相关资源
      最近更新 更多