从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