从bash模拟“group by”的最佳方法是什么?

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

10.0.10.1 10.0.10.1 10.0.10.3 10.0.10.2 10.0.10.1 

您需要一个shell脚本来计算每个IP地址出现在文件中的次数。 对于之前的input,您需要以下输出:

 10.0.10.1 3 10.0.10.2 1 10.0.10.3 1 

一种方法是:

 cat ip_addresses |uniq |while read ip do echo -n $ip" " grep -c $ip ip_addresses done 

然而,这远远没有效率。

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

(有一件事要补充:我知道它可以从perl或awk中解决,我对bash有更好的解决scheme感兴趣,而不是那些语言。)

附加信息:

假设源文件为5GB,运行该algorithm的机器为4GB。 所以sorting不是一个有效的解决scheme,也不是多次读取文件。

我喜欢类似散列表的解决scheme – 任何人都可以提供改进的解决scheme?

其他信息#2:

有些人问,为什么我会在bash中用比如perl更简单的方式来做这件事。 原因是在机器上,我不得不做这个Perl不适合我。 这是一个定制的linux机器,没有我习惯的大部分工具。 我认为这是一个有趣的问题。

所以,请不要责怪这个问题,如果你不喜欢,就忽略它。 🙂

 sort ip_addresses | uniq -c 

这将打印计数第一,但除此之外,它应该正是你想要的。

快速和肮脏的方法如下:

cat ip_addresses | sort -n | uniq -c

如果您需要使用bash中的值,则可以将整个命令分配给bashvariables,然后遍历结果。

PS

如果省略了sorting命令,您将不会得到正确的结果,因为uniq仅查看连续的相同行。

规范的解决scheme是另一个被访者提到的解决scheme:

 sort | uniq -c 

它比用Perl或awk编写的代码更短,更简洁。

你写的是你不想使用sorting,因为数据的大小大于机器的主内存大小。 不要低估Unixsorting命令的执行质量。 Sort用于处理128k(即131,072字节)内存(PDP-11)机器上的大量数据(请考虑原始AT&T的计费数据)。 当sorting遇到比预设限制更多的数据(通常调整到接近机器主存储器的大小)时,它将它在主存储器中读取的数据分类,并将其写入临时文件中。 然后重复下一个数据块的操作。 最后,它对这些中间文件执行合并sorting。 这允许sorting处理比机器主存储器多数倍的数据。

在一组现有字段的基础上总结多个字段,使用下面的例子:(根据您的要求replace$ 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 

看来你必须使用大量的代码来模拟bash中的哈希以获得线性行为,或者坚持二次超线性版本。

在这些版本中, Saua的解决scheme是最好的(也是最简单的):

 sort -n ip_addresses.txt | uniq -c 

我发现http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-11/0118.html 。 但它是丑陋的…

 cat ip_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}' 

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

您可能可以使用文件系统本身作为哈希表。 伪代码如下:

 for every entry in the ip address file; do let addr denote the ip address; if file "addr" does not exist; then create file "addr"; write a number "0" in the file; else read the number from "addr"; increase the number by 1 and write it back; fi done 

最后,您只需要遍历所有文件,并在文件中打印文件名和数字。 或者,不要保留一个计数,而是每次在文件中添加一个空格或一个换行符,最后只需查看文件大小(以字节为单位)。

解决scheme(像mysql一样)

 grep -ioh "facebook\|xing\|linkedin\|googleplus" access-log.txt | sort | uniq -c | sort -n 

结果

 3249 googleplus 4211 linkedin 5212 xing 7928 facebook 

我知道你正在寻找Bash中的某些东西,但是如果其他人可能正在寻找Python中的东西,你可能想要考虑这个:

 mySet = set() for line in open("ip_address_file.txt"): line = line.rstrip() mySet.add(line) 

由于默认情况下,集合中的值是唯一的,而且Python在这方面非常好,所以在这里你可能会赢得一些东西。 我没有testing代码,所以它可能会被窃听,但这可能会让你在那里。 如果你想计算出现次数,使用一个字典而不是一个集合很容易实现。

编辑:我是一个糟糕的读者,所以我回答错了。 这是一个字典,可以计算出现的字典。

 mydict = {} for line in open("ip_address_file.txt"): line = line.rstrip() if line in mydict: mydict[line] += 1 else: mydict[line] = 1 

字典mydict现在拥有一个唯一的IP作为关键字的列表,以及它们作为它们的值发生的次数。

在这种情况下我觉得awk关联数组也很方便

 $ awk '{count[$1]++}END{for(j in count) print j,count[j]}' ips.txt 

一个小组通过邮寄在这里

我会这样做:

 perl -e 'while (<>) {chop; $h{$_}++;} for $k (keys %h) {print "$k $h{$k}\n";}' ip_addresses 

但uniq可能会为你工作。

大多数其他解决scheme计数重复。 如果您确实需要对键值对进行分组,请尝试以下操作:

这是我的示例数据:

 find . | xargs md5sum fe4ab8e15432161f452e345ff30c68b0 a.txt 30c68b02161e15435ff52e34f4fe4ab8 b.txt 30c68b02161e15435ff52e34f4fe4ab8 c.txt fe4ab8e15432161f452e345ff30c68b0 d.txt fe4ab8e15432161f452e345ff30c68b0 e.txt 

这将打印由md5校验和分组的键值对。

 cat table.txt | awk '{print $1}' | sort | uniq | xargs -i grep {} table.txt 30c68b02161e15435ff52e34f4fe4ab8 b.txt 30c68b02161e15435ff52e34f4fe4ab8 c.txt fe4ab8e15432161f452e345ff30c68b0 a.txt fe4ab8e15432161f452e345ff30c68b0 d.txt fe4ab8e15432161f452e345ff30c68b0 e.txt 

如果订单不重要,可以省略sorting

 uniq -c <source_file> 

要么

 echo "$list" | uniq -c 

如果源列表是一个variables