【问题标题】:Awk: Sum up column values across multiple files with identical column layoutawk:汇总具有相同列布局的多个文件中的列值
【发布时间】:2017-11-19 17:12:56
【问题描述】:

我有许多具有相同标题的文件:

COL1、COL2、COL3、COL4

您可以忽略 COL1-COL3。 COL4 包含一个数字。每个文件包含大约 200 行。我试图总结行。例如:

文件 1

COL1 COL2 COL3 COL4
 x    y   z    3
 a    b   c    4

文件 2

COL1 COL2 COL3 COL4
 x     y    z   5 
 a     b    c   10 

然后返回一个新文件:

COL1 COL2 COL3 COL4
 x     y    z   8 
 a     b    c   14

有没有一种简单的方法可以做到这一点没有 AWK?如果需要,我会使用 AWK,我只是想可能有一个简单的单线,我可以立即运行。我想到的 AWK 脚本感觉有点长。

谢谢

【问题讨论】:

  • 所有文件中的 COL1-3 是否相同?它们在所有文件中出现的顺序是否相同?
  • awk 将是一个不错的最佳选择。当然,不是短行,而是……两行,是的,我会用两行来写
  • 你在往回想 - 在任何给定的文本操作情况下,可能存在一个长度或复杂的解决方案,awk 更简单的方法。

标签: linux awk gawk


【解决方案1】:

pasteawk 结合,如Kristo Mägi's answer,是您最好的选择:

  • paste 合并输入文件中的对应行,
  • 将单个输入行流发送到awk,每个输入行包含要汇总的所有字段。

假设输入文件和列的数量固定,Kristo 的答案可以简化为(使处理更加高效):

paste file1 file2 | awk '{ print $1, $2, $3, (NR==1 ? $4 : $4 + $8) }'

注意:以上产生 空格 分隔的输出列,因为awk 的默认值OFS 是输出字段分隔符,是一个空格。支持>


假设所有文件具有相同的列结构和行数,下面是解决方案的概括,其中:

  • 概括为超过 2 个输入文件(以及超过 2 个数据行)
  • 概括为任意数量的字段,只要要总结的字段是最后一个
#!/bin/bash

files=( file1 file2 ) # array of input files
paste "${files[@]}" | awk -v numFiles=${#files[@]} -v OFS='\t' '
  {
    row = sep = ""
    for(i=1; i < NF/numFiles; ++i) { row = row sep $i; sep = OFS }
    sum = $(NF/numFiles) # last header col. / (1st) data col. to sum
    if (NR > 1) { for(i=2; i<=numFiles; ++i) sum += $(NF/numFiles * i) } # add other cols.
    printf "%s%s%s\n", row, OFS, sum
  }
'

请注意,\t(制表符字符)用于分隔输出字段,并且由于依赖于 awk 的默认行拆分为字段,因此无法保证在字段之间保留准确的输入空格。

【讨论】:

    【解决方案2】:

    如果所有文件都有相同的 header - awk 解决方案:

    awk '!f && FNR==1{ f=1; print $0 }FNR>1{ s[FNR]+=$NF; $NF=""; r[FNR]=$0 }
          END{ for(i=2;i<=FNR;i++) print r[i],s[i] }' File[12]
    

    输出(2个文件):

    COL1 COL2 COL3 COL4
    x y z 8
    a b c 14
    

    这种方法可以应用于多个文件(在这种情况下,您可以指定 globbing File* 用于文件名扩展

    【讨论】:

      【解决方案3】:

      还有一个选择。

      命令:

      paste f{1,2}.txt | sed '1d' | awk '{print $1,$2,$3,$4+$8}' | awk 'BEGIN{print "COL1","COL2","COL3","COL4"}1'
      

      结果:

      COL1 COL2 COL3 COL4
      x y z 8
      a b c 14
      

      它的作用:

      测试文件:

      $ cat f1.txt
      COL1 COL2 COL3 COL4
       x    y   z    3
       a    b   c    4
      
      $ cat f2.txt
      COL1 COL2 COL3 COL4
       x     y    z   5
       a     b    c   10
      

      命令:paste f{1,2}.txt
      连接 2 个文件并给出输出:

      COL1 COL2 COL3 COL4 COL1 COL2 COL3 COL4
       x    y   z    3     x     y    z   5
       a    b   c    4     a     b    c   10
      

      命令:sed '1d'
      旨在暂时删除标题

      命令:awk '{print $1,$2,$3,$4+$8}'
      从粘贴结果返回 COL1-3 和 $4 和 $8 的总和。

      命令:awk 'BEGIN{print "COL1","COL2","COL3","COL4"}1'
      向后添加标题

      编辑:
      在@mklement0 评论之后,他对标题处理是正确的,因为我忘记了NR==1 部分。

      所以,我也会在这里代理他的更新版本:

      paste f{1,2}.txt | awk '{ print $1, $2, $3, (NR==1 ? $4 : $4 + $8) }'
      

      【讨论】:

      • OP 说I have a number of files...。此解决方案仅在该数字恰好为 2 时才有效,因此 YMMV 与真实世界的数据一致。
      • @EdMorton 没有指定如何传递文件的数量。该解决方案很容易扩展,可以在给定的情况下为pasteawk 提供正确的输入:) 所以,它与现实世界的数据相差不远;)
      • 如果您尝试扩展它以容纳更多文件,您会很头疼处理那些硬编码的标题字符串,一旦您修复它,它就会成为其他发布的解决方案之一。另外,当您使用 awk 时,您根本不需要 sed,也不需要 awk 命令的管道。这根本不是一个好方法。
      • 作为参考,我有大约 23 个文件。我以为我只会有一个文件作为“基本文件”,然后我将每个文件都放在基本文件之上。
      • @TimelordViktorious 这将是一种效率极低、脆弱且完全不必要的方法。您在这里有多个答案,可以一次汇总所有文件的值。
      【解决方案4】:

      您说您有“许多文件”。即,超过 2 个。

      鉴于这 3 个文件(并且应该使用任意数量):

      $ cat f1 f2 f3
      COL1 COL2 COL3 COL4
       x    y   z    3
       a    b   c    4
      COL1 COL2 COL3 COL4
       x     y    z   5 
       a     b    c   10 
      COL1 COL2 COL3 COL4
       x     y    z   10 
       a     b    c   15 
      

      你可以这样做:

      $ awk 'FNR==1{next}
           {sum[$1]+=$4}
           END{print "COL1 COL4"; 
               for (e in sum) print e, sum[e]} ' f1 f2 f3
      COL1 COL4
      x 18
      a 29
      

      不清楚你打算用 COL2 或 COL3 做什么,所以我没有添加。

      【讨论】:

        【解决方案5】:
        $ awk '
             NR==1 { print }
             { sum[FNR]+=$NF; sub(/[^[:space:]]+[[:space:]]*$/,""); pfx[FNR]=$0 }
             END { for(i=2;i<=FNR;i++) print pfx[i] sum[i] }
        ' file1 file2
        COL1 COL2 COL3 COL4
         x     y    z   8
         a     b    c   14
        

        以上内容将在任何 UNIX 系统上的任何 awk、任何数量的输入文件和这些文件的任何内容中稳健有效地工作。唯一的潜在问题是它必须在内存中保留相当于其中 1 个文件的内容,因此如果每个文件都非常庞大,那么您可能会耗尽可用内存。

        【讨论】:

        • 为了避免为每个输入文件重新创建pfx,可能值得用NR == FNR条件来保护它。
        • @mklement0 是的,应该是 $NF,现在更正,谢谢。是的,我可以添加 NR==FNR 条件,但老实说我怀疑它会产生明显的不同,所以我不会打扰。
        • @EdMorton 如果想要对多列中的条目进行求和,而不仅仅是特定列的条目,会发生什么。
        • @Boogeyman 取决于要求,请提出一个新问题。
        猜你喜欢
        • 2018-08-03
        • 1970-01-01
        • 1970-01-01
        • 2021-08-05
        • 1970-01-01
        • 2014-06-23
        • 2018-12-31
        • 2017-09-10
        • 2015-10-05
        相关资源
        最近更新 更多