“模拟的最佳方式”;分组方式;来自bash?

“模拟的最佳方式”;分组方式;来自bash?,bash,scripting,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_addr

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


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

快速脏法如下:

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
cat ip_地址| sort-n | uniq-c

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

PS

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

sort ip_addresses | uniq -c

这将首先打印计数,但除此之外,它应该是您想要的。如果顺序不重要,则可以省略排序

uniq -c <source_file>

如果源列表是一个变量

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

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


我找到了。但是它像地狱一样丑陋…

我会这样做:

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

但是Unq可能为你工作。

< P>我知道你在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作为键的列表,以及它们作为值出现的次数。

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

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

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

标准解是另一位受访者提到的:

sort | uniq -c
它比用Perl或awk编写的代码更短、更简洁


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

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

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

根据一组现有字段汇总多个字段时,请使用以下示例:(根据您的要求替换$1、$2、$3、$4)

解决方案(类似mysql的分组)

结果

3249  googleplus
4211 linkedin
5212 xing
7928 facebook

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

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

以下是我的示例数据:

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
纯(没有叉子!) 有一种方法,使用函数。这条路很快,因为没有叉子

。。。而一堆ip地址仍然很小

countIp(){
本地-a _ips=();本地_a
而IFS=.read-a\u a;do
(($$ips[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\}
完成
}
注意:IP地址转换为32位无符号整数值,用作数组的索引。这使用简单的bash数组,而不是关联数组(它更昂贵)

time countIp

在我的主机上,这样做要比使用fork快得多,最多大约有1000个地址,但当我尝试对10000个地址进行排序时,大约需要1整秒钟。

从效率角度看,这非常相似,您仍然具有二次行为二次意义O(n^2)?这取决于排序算法,当然,不太可能使用这样的bogo排序。好吧,在最好的情况下是O(n)
3249  googleplus
4211 linkedin
5212 xing
7928 facebook
cat ip_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}'
find . | xargs md5sum
fe4ab8e15432161f452e345ff30c68b0 a.txt
30c68b02161e15435ff52e34f4fe4ab8 b.txt
30c68b02161e15435ff52e34f4fe4ab8 c.txt
fe4ab8e15432161f452e345ff30c68b0 d.txt
fe4ab8e15432161f452e345ff30c68b0 e.txt
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
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
}
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