【问题标题】:GROUP BY/SUM from shell来自 shell 的 GROUP BY/SUM
【发布时间】:2012-05-04 10:01:10
【问题描述】:

我有一个包含如下数据的大文件:

a 23
b 8
a 22
b 1

我希望能够得到这个:

a 45
b 9

我可以先对该文件进行排序,然后在 Python 中扫描该文件一次。有什么好的直接命令行方式来执行此操作?

【问题讨论】:

    标签: linux shell unix command-line awk


    【解决方案1】:

    编辑:现代 (GNU/Linux) 解决方案,正如几年前在 cmets 中提到的 ;-)。

    awk '{
        arr[$1]+=$2
       }
       END {
         for (key in arr) printf("%s\t%s\n", key, arr[key])
       }' file \
       | sort -k1,1
    

    最初发布的解决方案,基于旧的 Unix sort 选项:

    awk '{
        arr[$1]+=$2
       }
       END {
         for (key in arr) printf("%s\t%s\n", key, arr[key])
       }' file \
       | sort +0n -1
    

    我希望这会有所帮助。

    【讨论】:

    • 这些参数究竟对排序有什么作用?我在手册页中看不到它们,调用页面让我感到困惑。
    • 现代版本的排序更喜欢-k 语法来指定排序键:sort -nk1,1 而不是sort +0n -1。但是既然键是字母,那你为什么还要指定-n呢?
    • @EricR:+0n -1 是老式的 -n -k1,1:按第一个(空格分隔)字段进行数字排序。
    • 您也可以让 awk 进行排序:asorti(arr,keys); for (i in keys) { printf "%s\t%s\n", keys[i], arr[keys[i]]);
    • @MarkReed 你可以,但是如果想要扩展到非常大的输入(特别是如果这些输入大于可用内存时,GNU 排序将是更有效的方法;在这种情况下,GNU 排序将分区为临时文件)
    【解决方案2】:

    这里不需要 awk,甚至不需要排序——如果你有 Bash 4.0,你可以使用关联数组:

    #!/bin/bash
    declare -A values
    while read key value; do
      values["$key"]=$(( $value + ${values[$key]:-0} ))
    done
    for key in "${!values[@]}"; do
      printf "%s %s\n" "$key" "${values[$key]}"
    done
    

    ...或者,如果您首先对文件进行排序(这将更节省内存;GNU sort 能够对大于内存的文件进行排序,这是一个天真的脚本——无论是在 awk、python 还是 shell -- 通常不会),您可以通过在旧版本中工作的方式执行此操作(我希望以下内容可以通过 bash 2.0 工作):

    #!/bin/bash
    read cur_key cur_value
    while read key value; do
      if [[ $key = "$cur_key" ]] ; then
        cur_value=$(( cur_value + value ))
      else
        printf "%s %s\n" "$cur_key" "$cur_value"
        cur_key="$key"
        cur_value="$value"
      fi
    done
    printf "%s %s\n" "$cur_key" "$cur_value"
    

    【讨论】:

    • 见鬼,上面的一些最低限度的修改可以在 vanilla Bourne 中工作,不需要 bash。while read key value; do if [ "$key" = "$cur_key" ]; then cur_value=`expr $cur_value + $value`; else echo "$cur_key $cur_value"; cur_key="$key"; cur_value="$value"; fi; done; echo "$cur_key $cur_value"
    • @MarkReed,确实如此,尽管运行expr 的子shell 的性能影响足以使POSIX sh $(( )) 扩展会更好; (( )) 是 bash 扩展,$(( )) 符合标准;它只是需要 expr 的 1991 年之前的 POSIX 标准 Bourne sh。
    • 另一种(特定于 Bash,更简洁)的方式来表达第一个示例中的附加值:(( values["$key"] += value )) 注意:不需要 default-0。
    【解决方案3】:

    这个 Perl 单行似乎可以完成这项工作:

    perl -nle '($k, $v) = split; $s{$k} += $v; END {$, = " "; foreach $k (sort keys %s) {print $k, $s{$k}}}' inputfile
    

    【讨论】:

      【解决方案4】:

      这可以通过以下单线轻松实现:

      cat /path/to/file | termsql "SELECT col0, SUM(col1) FROM tbl GROUP BY col0"
      

      或者。

      termsql -i /path/to/file "SELECT col0, SUM(col1) FROM tbl GROUP BY col0"
      

      这里使用了一个 Python 包,termsql,它是 SQLite 的包装器。注意,目前还不能上传到PyPI,也只能全系统安装(setup.py有点破),比如:

      pip install --user https://github.com/tobimensch/termsql/archive/master.zip
      

      更新

      2020年1.0版终于上传到PyPI,所以pip install --user termsql可以用了。

      【讨论】:

      • 我喜欢 termsql 工具!它可以很方便!
      【解决方案5】:

      一种使用perl的方式:

      perl -ane '
          next unless @F == 2; 
          $h{ $F[0] } += $F[1]; 
          END { 
              printf qq[%s %d\n], $_, $h{ $_ } for sort keys %h;
          }
      ' infile
      

      infile的内容:

      a 23
      b 8
      a 22
      b 1
      

      输出:

      a 45
      b 9
      

      【讨论】:

        【解决方案6】:

        使用 GNU awk(版本低于 4):

        WHINY_USERS= awk 'END {
          for (E in a)
            print E, a[E]
            }
        { a[$1] += $2 }' infile
        

        使用 GNU awk >= 4:

        awk 'END {
          PROCINFO["sorted_in"] = "@ind_str_asc"
          for (E in a)
            print E, a[E]
            }
        { a[$1] += $2 }' infile
        

        【讨论】:

        【解决方案7】:

        使用sort + awk 组合可以尝试关注,而不创建数组。

        sort -k1 Input_file | 
        awk '
          prev!=$1 && prev{
            print prev,(prevSum?prevSum:"N/A")
            prev=prevSum=""
          }
          {
            prev=$1
            prevSum+=$2
          }
          END{
            if(prev){
               print prev,(prevSum?prevSum:"N/A")
            }
        }'
        

        说明:为上述添加详细说明。

        sort -k1 file1 |                          ##Using sort command to sort Input_file by 1st field and sending output to awk as an input.
        awk '                                     ##Starting awk program from here.
          prev!=$1 && prev{                       ##Checking condition prev is NOT equal to first field and prev is NOT NULL.
            print prev,(prevSum?prevSum:"N/A")    ##Printing prev and prevSum(if its NULL then print N/A).
            prev=prevSum=""                       ##Nullify prev and prevSum here.
          }
          {
            prev=$1                               ##Assigning 1st field to prev here.
            prevSum+=$2                           ##Adding 2nd field to prevSum.
          }
          END{                                    ##Starting END block of this awk program from here.
            if(prev){                             ##Checking condition if prev is NOT NULL then do following.
               print prev,(prevSum?prevSum:"N/A") ##Printing prev and prevSum(if its NULL then print N/A).
            }
        }'
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-01-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多