假设你有一个包含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机器,没有我使用过的大多数工具。我认为这是一个有趣的问题。

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


当前回答

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

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

它使用临时分组/排序前缀,在排序后删除;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

其他回答

又快又脏的方法如下:

Cat ip_addresses | sort -n | uniq -c

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

PS

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

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

将数据导入sqlite db并使用sql语法(只是另一个想法)。 我知道这对于这个例子来说太多了,但是对于有多个文件(表)的复杂查询是有用的

#!/bin/bash
trap clear_db EXIT
clear_db(){ rm -f "mydb$$"; }

# add header to input_file (IP)
INPUT_FILE=ips.txt

# import file into db
sqlite3 -csv mydb$$ ".import ${INPUT_FILE} mytable"

# using sql statements on table 'mytable' 
sqlite3 mydb$$ -separator " "  "SELECT IP, COUNT(*) FROM mytable GROUP BY IP;"

10.0.10.1 3
10.0.10.2 1
10.0.10.3 1
sort ip_addresses | uniq -c

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

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

uniq -c <source_file>

or

echo "$list" | uniq -c

如果源列表是一个变量