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

时间:2008-12-19 12:13:58

标签: bash scripting

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

10.0.10.1
10.0.10.1
10.0.10.3
10.0.10.2
10.0.10.1

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

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中的更好解决方案感兴趣,而不是那些语言。)

附加信息:

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

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

附加信息#2:

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

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

14 个答案:

答案 0 :(得分:360)

sort ip_addresses | uniq -c

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

答案 1 :(得分:44)

快速而肮脏的方法如下:

cat ip_addresses | sort -n | uniq -c

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

PS

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

答案 2 :(得分:20)

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

sort | uniq -c

它比Perl或awk中的内容更简洁,更简洁。

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

答案 3 :(得分:16)

根据一组现有字段汇总多个字段,请使用以下示例:(根据您的要求替换$ 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

答案 4 :(得分:8)

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

此命令将为您提供所需的输出

答案 5 :(得分:4)

似乎您必须使用大量代码来模拟bash中的哈希才能获得线性行为或坚持二次超线性版本。

在这些版本中,saua的解决方案是最好的(也是最简单的):

sort -n ip_addresses.txt | uniq -c

我找到了http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-11/0118.html。但是它太丑了......

答案 6 :(得分:3)

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

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

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

答案 7 :(得分:3)

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

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

按邮件here

分组

答案 8 :(得分:3)

解决方案(像mysql一样分组)

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

结果

3249  googleplus
4211 linkedin
5212 xing
7928 facebook

答案 9 :(得分:1)

(无叉!)

有一种方法,使用 函数。这种方式非常快,因为没有分叉!...

...虽然一堆 ip地址保持

countIp () { 
    local -a _ips=(); local _a
    while IFS=. read -a _a ;do
        ((_ips[_a<<24|${_a[1]}<<16|${_a[2]}<<8|${_a[3]}]++))
    done
    for _a in ${!_ips[@]} ;do
        printf "%.16s %4d\n" \
          $(($_a>>24)).$(($_a>>16&255)).$(($_a>>8&255)).$(($_a&255)) ${_ips[_a]}
    done
}

注意:IP地址转换为32位无符号整数值,用作数组的索引。这使用简单的 bash数组 ,而不是 关联数组 (价格更贵)!

time countIp < ip_addresses 
10.0.10.1    3
10.0.10.2    1
10.0.10.3    1
real    0m0.001s
user    0m0.004s
sys     0m0.000s

time sort ip_addresses | uniq -c
      3 10.0.10.1
      1 10.0.10.2
      1 10.0.10.3
real    0m0.010s
user    0m0.000s
sys     0m0.000s

在我的主机上,这样做比使用分叉要快得多,最多大约1 000个地址,但是当我尝试排序计数 10'000时,需要大约1秒钟地址。

答案 10 :(得分:0)

我这样做了:

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

但uniq可能适合你。

答案 11 :(得分:0)

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

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

由于默认情况下集合中的值是唯一的,并且Python非常擅长这些东西,所以你可能会在这里赢得一些东西。我没有测试过代码,因此它可能会被窃听,但这可能会让你感到满意。如果你想计算出现次数,使用dict而不是set就很容易实现。

编辑: 我是一个糟糕的读者,所以我回答错了。这是一个带有dict的片段,可以计算出现的情况。

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列表作为键,以及它们作为值发生的次数。

答案 12 :(得分:0)

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

以下是我的示例数据:

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

答案 13 :(得分:-7)

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

uniq -c <source_file>

echo "$list" | uniq -c

如果源列表是变量