假设你有一个包含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中做,而在例如perl中更容易。原因是,在机器上,我必须这样做perl是不可为我。这是一台定制的linux机器,没有我使用过的大多数工具。我认为这是一个有趣的问题。

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


当前回答

又快又脏的方法如下:

Cat ip_addresses | sort -n | uniq -c

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

PS

如果省略sort命令,就不会得到正确的结果,因为uniq只查看连续的相同行。

其他回答

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

这个命令将提供您想要的输出

这并没有回答原始问题的计数元素,但这个问题是搜索引擎在搜索我想要实现的东西时的第一个结果,所以我认为这可能会帮助一些人,因为它与“分组”功能有关。

我想根据它们的分组来排序文件,其中文件名中存在的一些字符串决定了组。

它使用临时分组/排序前缀,在排序后删除;Sed替换表达式(s#pattern#replacement#g)匹配目标字符串,并在目标字符串所需排序顺序对应的行前加上一个整数。然后,使用cut去除分组前缀。

注意,sed表达式可以被连接(例如,sed -e '<expr>;< expr >;<expr>;')但这里为了可读性将它们分开。

它不漂亮,可能也不快(我处理的项目少于50项),但它至少在概念上简单,不需要学习awk。

#!/usr/bin/env bash

for line in $(find /etc \
    | sed -E -e "s#^(.*${target_string_A}.*)#${target_string_A_sort_index}:\1#;" \
    | sed -E -e "s#^(.*${target_string_B}.*)#${target_string_B_sort_index}:\1#;" \
    | sed -E -e "s#^/(.*)#00:/\1#;" \
    | sort \
    | cut -c4-
)
do
    echo "${line}"
done

例如输入

/this/is/a/test/a
/this/is/a/test/b
/this/is/a/test/c
/this/is/a/special/test/d
/this/is/a/another/test/e
#!/usr/bin/env bash

for line in $(find /etc \
    | sed -E -e "s#^(.*special.*)#10:\1#;" \
    | sed -E -e "s#^(.*another.*)#05:\1#;" \
    | sed -E -e "s#^/(.*)#00:/\1#;" \
    | sort \
    | cut -c4-
)
do
    echo "${line}"
done
/this/is/a/test/a
/this/is/a/test/b
/this/is/a/test/c
/this/is/a/another/test/e
/this/is/a/special/test/d
sort ip_addresses | uniq -c

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

我知道你在Bash中寻找一些东西,但如果其他人可能在Python中寻找一些东西,你可能会考虑这样做:

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

由于集合中的值在默认情况下是唯一的,而Python在这方面非常擅长,因此您可能会在这里赢得一些东西。我还没有测试代码,所以它可能有漏洞,但这可能会让你明白。如果你想要计数出现的次数,使用字典而不是集合很容易实现。

编辑: 我不擅长阅读,所以我答错了。这里有一个字典片段,可以计算发生的次数。

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的列表作为键,它们出现的次数作为值。

解决方案(分组如mysql)

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

结果

3249  googleplus
4211 linkedin
5212 xing
7928 facebook