如何计算文件中文本的出现次数?


19

我有一个按IP地址排序的日志文件,我想查找每个唯一IP地址的出现次数。我如何用bash做到这一点?可能列出IP旁边的出现次数,例如:

5.135.134.16 count: 5
13.57.220.172: count 30
18.206.226 count:2

等等。

这是日志示例:

5.135.134.16 - - [23/Mar/2019:08:42:54 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
5.135.134.16 - - [23/Mar/2019:08:42:55 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
5.135.134.16 - - [23/Mar/2019:08:42:55 -0400] "POST /wp-login.php HTTP/1.1" 200 3836 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
5.135.134.16 - - [23/Mar/2019:08:42:55 -0400] "POST /wp-login.php HTTP/1.1" 200 3988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
5.135.134.16 - - [23/Mar/2019:08:42:56 -0400] "POST /xmlrpc.php HTTP/1.1" 200 413 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:05 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:06 -0400] "POST /wp-login.php HTTP/1.1" 200 3985 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:07 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:08 -0400] "POST /wp-login.php HTTP/1.1" 200 3833 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:09 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:11 -0400] "POST /wp-login.php HTTP/1.1" 200 3836 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:12 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:15 -0400] "POST /wp-login.php HTTP/1.1" 200 3837 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:17 -0400] "POST /xmlrpc.php HTTP/1.1" 200 413 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.233.99 - - [23/Mar/2019:04:17:45 -0400] "GET / HTTP/1.1" 200 25160 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"
18.206.226.75 - - [23/Mar/2019:21:58:07 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "https://www.google.com/url?3a622303df89920683e4421b2cf28977" "Mozilla/5.0 (Windows NT 6.2; rv:33.0) Gecko/20100101 Firefox/33.0"
18.206.226.75 - - [23/Mar/2019:21:58:07 -0400] "POST /wp-login.php HTTP/1.1" 200 3988 "https://www.google.com/url?3a622303df89920683e4421b2cf28977" "Mozilla/5.0 (Windows NT 6.2; rv:33.0) Gecko/20100101 Firefox/33.0"
18.213.10.181 - - [23/Mar/2019:14:45:42 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
18.213.10.181 - - [23/Mar/2019:14:45:42 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
18.213.10.181 - - [23/Mar/2019:14:45:42 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"

1
对于“ bash”,您一般是指普通外壳还是命令行?
甜点

1
您有可用的数据库软件吗?
SpacePhoenix


日志来自appache2服务器,而不是数据库。在一般用例中,bash是我希望的。我看到了python和perl解决方案,如果它们对其他人有用,那就太好了。sort -V虽然我认为这不是必需的,但最初的排序是通过完成的。我将登录页面的前十名滥用者发送给系统管理员,并提供了禁止各自子网的建议。例如,一个IP超过9000次点击登录页面。该IP及其D类子网现已列入黑名单。我敢肯定我们可以使它自动化,尽管那是另一个问题。
j0h

Answers:


13

您可以使用grepuniq作为地址列表,遍历它们,然后grep再次进行计数:

for i in $(<log grep -o '^[^ ]*' | uniq); do
  printf '%s count %d\n' "$i" $(<log grep -c "$i")
done

grep -o '^[^ ]*'输出从开始(^)到每行第一行的每个字符,uniq删除重复的行,从而为您提供IP地址列表。由于使用了命令替换,for循环在该列表上循环打印当前正在处理的IP,然后打印“ count”和count。后者是由计算的grep -c,该计数计算至少一个匹配项的行数。

运行示例

$ for i in $(<log grep -o '^[^ ]*'|uniq);do printf '%s count %d\n' "$i" $(<log grep -c "$i");done
5.135.134.16 count 5
13.57.220.172 count 9
13.57.233.99 count 1
18.206.226.75 count 2
18.213.10.181 count 3

13
此解决方案反复对输入文件进行迭代,对每个IP地址重复一次,如果文件很大,这将非常慢。使用uniq -cawk仅需要读取一次文件的其他解决方案,
David

1
@David这是真的,但这也是我第一次尝试,因为知道grep很重要。除非性能明显可测,否则...不要过早优化吗?
D. Ben Knoble

3
鉴于更有效的解决方案也更简单,但对于每个人来说,我不会称其为过早的优化。
大卫

顺便说一句,为什么写成as <log grep ...而不是grep ... log
圣地亚哥

@圣地亚哥因为这在许多方面都更好,如StéphaneChazelas在U&L上的解释
甜点

39

您可以使用cutuniq工具:

cut -d ' ' -f1 test.txt  | uniq -c
      5 5.135.134.16
      9 13.57.220.172
      1 13.57.233.99
      2 18.206.226.75
      3 18.213.10.181

说明:

  • cut -d ' ' -f1 :提取第一个字段(IP地址)
  • uniq -c :报告重复的行并显示发生的次数

6
可以使用sed,例如sed -E 's/ *(\S*) *(\S*)/\2 count: \1/'获得与OP完全相同的输出。
甜点

2
这应该是一个可以接受的答案,因为甜品店的一个人需要重复读取文件,因此速度要慢得多。sort file | cut .... 如果不确定文件是否已排序,可以轻松使用。
Guntram Blohm

14

如果您不特别要求给定的输出格式,那么我建议您使用已发布cut+ uniq的答案

如果您确实需要给定的输出格式,则可以在Awk中使用单遍方法

awk '{c[$1]++} END{for(i in c) print i, "count: " c[i]}' log

当输入已经被排序时,这是不理想的,因为它不必要地将所有IP存储到内存中-在预排序的情况下(虽然更直接等效于uniq -c),一种更好但更复杂的方法是:

awk '
  NR==1 {last=$1} 
  $1 != last {print last, "count: " c[last]; last = $1} 
  {c[$1]++} 
  END {print last, "count: " c[last]}
'

例如

$ awk 'NR==1 {last=$1} $1 != last {print last, "count: " c[last]; last = $1} {c[$1]++} END{print last, "count: " c[last]}' log
5.135.134.16 count: 5
13.57.220.172 count: 9
13.57.233.99 count: 1
18.206.226.75 count: 2
18.213.10.181 count: 3

使用sed更改基于cut + uniq的答案会很容易以所需的格式显示。
彼得-

@ PeterA.Schneider是的,我相信
钢铁驾驶员

啊,是的,我明白了。
彼得-恢复莫妮卡

8

这是一种可能的解决方案:

IN_FILE="file.log"
for IP in $(awk '{print $1}' "$IN_FILE" | sort -u)
do
    echo -en "${IP}\tcount: "
    grep -c "$IP" "$IN_FILE"
done
  • file.log用实际的文件名替换。
  • 命令替换表达式$(awk '{print $1}' "$IN_FILE" | sort -u)将提供第一列的唯一值的列表。
  • 然后grep -c将计算文件中的每个值。

$ IN_FILE="file.log"; for IP in $(awk '{print $1}' "$IN_FILE" | sort -u); do echo -en "${IP}\tcount: "; grep -c "$IP" "$IN_FILE"; done
13.57.220.172   count: 9
13.57.233.99    count: 1
18.206.226.75   count: 2
18.213.10.181   count: 3
5.135.134.16    count: 5

1
更喜欢printf...
D. Ben Knoble

1
这意味着您需要多次处理整个文件。一次获取IP列表,然后再次为找到的每个IP列表。
Terdon

5

一些Perl:

$ perl -lae '$k{$F[0]}++; }{ print "$_ count: $k{$_}" for keys(%k)' log 
13.57.233.99 count: 1
18.206.226.75 count: 2
13.57.220.172 count: 9
5.135.134.16 count: 5
18.213.10.181 count: 3

这与Steeldriver的awk方法相同,但是在Perl中。的-a原因Perl来每个输入行自动分割成阵列@F,其第一个元素(IP)是$F[0]。因此,$k{$F[0]}++将创建hash %k,其键是IP,其值是看到每个IP的次数。的}{是“做休息在最后,加工后输入”时髦perlspeak。因此,最后,脚本将遍历哈希键,并打印当前键($_)及其值($k{$_})。

而且,这样一来,人们就不会认为perl会迫使您编写看起来像是暗写的脚本,这是同一件事,但不那么精简:

perl -e '
  while (my $line=<STDIN>){
    @fields = split(/ /, $line);
    $ip = $fields[0];
    $counts{$ip}++;
  }
  foreach $ip (keys(%counts)){
    print "$ip count: $counts{$ip}\n"
  }' < log

4

也许这不是OP想要的;但是,如果我们知道IP地址的长度将限制为15个字符,则可以uniq仅使用以下命令来实现从庞大的日志文件中显示具有唯一IP的计数的更快方法:

$ uniq -w 15 -c log

5 5.135.134.16 - - [23/Mar/2019:08:42:54 -0400] ...
9 13.57.220.172 - - [23/Mar/2019:11:01:05 -0400] ...
1 13.57.233.99 - - [23/Mar/2019:04:17:45 -0400] ...
2 18.206.226.75 - - [23/Mar/2019:21:58:07 -0400] ...
3 18.213.10.181 - - [23/Mar/2019:14:45:42 -0400] ...

选项:

-w N只比较N行中的字符

-c 将以出现的次数为行加前缀

另外,对于我希望精确格式化的输出awk(也应适用于IPV6地址),ymmv。

$ awk 'NF { print $1 }' log | sort -h | uniq -c | awk '{printf "%s count: %d\n", $2,$1 }'

5.135.134.16 count: 5
13.57.220.172 count: 9
13.57.233.99 count: 1
18.206.226.75 count: 2
18.213.10.181 count: 3

请注意,uniq如果输入文件中的重复行不相邻,则不会检测到它们,因此sort该文件可能有必要。


1
在实践中可能足够好,但值得注意的是极端情况。IP` --[`之后仅6个可能为常数的字符。但是从理论上讲,该地址最多可以比最大长度短8个字符,因此更改日期可以将此类IP的数量分开。就像您暗示的那样,这不适用于IPv6。
马丁·桑顿

我喜欢它,我不知道uniq可以算!
j0h

1

FWIW,Python 3:

from collections import Counter

with open('sample.log') as file:
    counts = Counter(line.split()[0] for line in file)

for ip_address, count in counts.items():
    print('%-15s  count: %d' % (ip_address, count))

输出:

13.57.233.99     count: 1
18.213.10.181    count: 3
5.135.134.16     count: 5
18.206.226.75    count: 2
13.57.220.172    count: 9

0
cut -f1 -d- my.log | sort | uniq -c

说明:将my.log的第一个字段以破折号分隔-并对其进行排序。uniq需要排序的输入。-c告诉它计算发生次数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.