来自 shell 的 GROUP BY/SUM
Posted
技术标签:
【中文标题】来自 shell 的 GROUP BY/SUM【英文标题】:GROUP BY/SUM from shell 【发布时间】:2012-05-04 10:01:10 【问题描述】:我有一个包含如下数据的大文件:
a 23
b 8
a 22
b 1
我希望能够得到这个:
a 45
b 9
我可以先对该文件进行排序,然后在 Python 中扫描该文件一次。有什么好的直接命令行方式来执行此操作?
【问题讨论】:
【参考方案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
【讨论】:
我似乎晚了将近三年才偶然发现这一点。WHINY_USERS
变量到底有什么作用?
对asciibetical order中的数组键进行排序。
链接死了,这个更有可能还活着:***.com/q/11697556/476716【参考方案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).
'
【讨论】:
以上是关于来自 shell 的 GROUP BY/SUM的主要内容,如果未能解决你的问题,请参考以下文章
linux shell:提取正则表达式捕获组(catch group)匹配的字符串
来自 GROUP_BY 的两个 LEFT JOIN 的 GROUP_CONCAT 的奇怪重复行为
如何在 Java 中获取 Group.Captures(来自 C# 中的 RegEx)的行为?