假设你有一个包含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机器,没有我使用过的大多数工具。我认为这是一个有趣的问题。
所以,请不要责怪这个问题,如果你不喜欢它,就忽略它。:-)
awk + sort(带版本排序标志)的组合可能是最快的(如果你的环境有awk的话):
echo "${input...}" |
{m,g}awk '{ __[$+_]++ } END { for(_ in __) { print "",+__[_],_ } }' FS='^$' OFS='\t' |
gsort -t$'\t' -k 3,3 -V
只有后GROUP-BY汇总行被发送到排序实用程序——与毫无理由地对输入行进行预先排序相比,这是一种系统密集型排序。
对于小输入,例如少于1000行左右,只需直接排序|uniq -c它。
3 10.0.10.1
1 10.0.10.2
1 10.0.10.3
这并没有回答原始问题的计数元素,但这个问题是搜索引擎在搜索我想要实现的东西时的第一个结果,所以我认为这可能会帮助一些人,因为它与“分组”功能有关。
我想根据它们的分组来排序文件,其中文件名中存在的一些字符串决定了组。
它使用临时分组/排序前缀,在排序后删除;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