从bash模拟"group by"的最佳方法?

Ziz*_*ncs 215 bash scripting

假设您有一个包含IP地址的文件,每行包含一个地址:

10.0.10.1
10.0.10.1
10.0.10.3
10.0.10.2
10.0.10.1
Run Code Online (Sandbox Code Playgroud)

您需要一个shell脚本,它为每个IP地址计算它在文件中出现的次数.对于先前的输入,您需要以下输出:

10.0.10.1 3
10.0.10.2 1
10.0.10.3 1
Run Code Online (Sandbox Code Playgroud)

一种方法是:

cat ip_addresses |uniq |while read ip
do
    echo -n $ip" "
    grep -c $ip ip_addresses
done
Run Code Online (Sandbox Code Playgroud)

然而,它真的远没有效率.

如何使用bash更有效地解决这个问题?

(有一点要补充:我知道它可以通过perl或awk解决,我对bash中的更好解决方案感兴趣,而不是那些语言.)

附加信息:

假设源文件为5GB,运行算法的机器为4GB.因此,排序不是一种有效的解决方案,也不是多次读取文件.

我喜欢类似哈希表的解决方案 - 任何人都可以对该解决方案进行改进吗?

附加信息#2:

有些人问为什么我会在bash中使用它时更加困难,例如perl.原因是在机器上我必须这样做perl不适合我.这是一个定制的linux机器,没有我习惯的大多数工具.我认为这是一个有趣的问题.

所以,请不要责怪这个问题,如果你不喜欢它就忽略它.:-)

Joa*_*uer 384

sort ip_addresses | uniq -c
Run Code Online (Sandbox Code Playgroud)

这将首先打印计数,但除此之外它应该是您想要的.

  • 然后你可以管道"sort -nr"按降序排序,从最高到最低计数.即`sort ip_addresses | uniq -c | sort -nr` (66认同)
  • 并且`排序ip_addresses | uniq -c | sort -nr | awk'{print $ 2,$ 1}'`获取第一列中的ip地址并计入第二列. (14认同)

小智 46

快速而肮脏的方法如下:

cat ip_addresses | sort -n | uniq -c

如果需要使用bash中的值,可以将整个命令分配给bash变量,然后遍历结果.

PS

如果省略sort命令,则无法获得正确的结果,因为uniq仅查看连续的相同行.

  • uuoc,无用的猫 (10认同)

小智 20

为了总结多个字段,基于一组现有字段,使用以下示例:(根据您的要求替换$ 1,$ 2,$ 3,$ 4)

cat file

US|A|1000|2000
US|B|1000|2000
US|C|1000|2000
UK|1|1000|2000
UK|1|1000|2000
UK|1|1000|2000

awk 'BEGIN { FS=OFS=SUBSEP="|"}{arr[$1,$2]+=$3+$4 }END {for (i in arr) print i,arr[i]}' file

US|A|3000
US|B|3000
US|C|3000
UK|1|9000
Run Code Online (Sandbox Code Playgroud)

  • 还有一件事,请注意 awk 的 print 函数似乎将 64 位整数缩小为 32 位,因此对于超过 2^31 的 int 值,您可能需要使用带有 %.0f 格式的 printf 而不是 print ` 那里 (3认同)
  • +1 因为它显示了不仅需要计数时要做什么 (2认同)
  • +1,因为“sort”和“uniq”最容易进行计数,但当您需要计算/求和字段值时没有帮助。awk 的数组语法非常强大,也是此处分组的关键。谢谢! (2认同)
  • 人们寻找字符串连接而不是数字加法的“group by”时,会将 `arr[$1,$2]+=$3+$4` 替换为 `arr[$1,$2]=(arr[$1,$2] $3 "," 4 美元)。我需要它来提供按包分组的文件列表(仅两列)并成功使用:“arr[$1]=(arr[$1] $2)”。 (2认同)

Dio*_*lis 19

规范解决方案是另一位受访者提到的解决方案:

sort | uniq -c
Run Code Online (Sandbox Code Playgroud)

它比Perl或awk中编写的更简洁,更简洁.

您写道,您不想使用排序,因为数据的大小大于计算机的主内存大小.不要低估Unix sort命令的实现质量.Sort用于处理具有128k(即131,072字节)内存(PDP-11)的计算机上的大量数据(比如原始AT&T的计费数据).当排序遇到的数据多于预设限制(通常调整到接近机器主存储器的大小)时,它会对它在主存储器中读取的数据进行排序并将其写入临时文件.然后它使用下一个数据块重复该操作.最后,它对这些中间文件执行合并排序.这允许排序处理比机器主存储器大许多倍的数据.


zjo*_*jor 9

cat ip_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}'
Run Code Online (Sandbox Code Playgroud)

这个命令会给你想要的输出


小智 6

解决方案(group by like mysql)

grep -ioh "facebook\|xing\|linkedin\|googleplus" access-log.txt | sort | uniq -c | sort -n
Run Code Online (Sandbox Code Playgroud)

结果

3249  googleplus
4211 linkedin
5212 xing
7928 facebook
Run Code Online (Sandbox Code Playgroud)