Làm thế nào để đếm số lần xuất hiện của văn bản trong một tập tin?


19

Tôi có một tệp nhật ký được sắp xếp theo địa chỉ IP, tôi muốn tìm số lần xuất hiện của từng địa chỉ IP duy nhất. Làm thế nào tôi có thể làm điều này với bash? Có thể liệt kê số lần xuất hiện bên cạnh một ip, chẳng hạn như:

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

vân vân

Đây là một mẫu của nhật ký:

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
Với bash, bạn có nghĩa là shell đơn giản hay dòng lệnh nói chung?
tráng miệng

1
Bạn có bất kỳ phần mềm cơ sở dữ liệu có sẵn để sử dụng?
SpacePhoenix


Nhật ký là từ một máy chủ appache2, không thực sự là một cơ sở dữ liệu. bash là những gì tôi muốn, trong trường hợp sử dụng chung. Tôi thấy các giải pháp python và perl, nếu chúng tốt cho người khác, điều đó thật tuyệt. việc sắp xếp ban đầu được thực hiện sort -Vmặc dù tôi nghĩ rằng điều đó là không bắt buộc. Tôi đã gửi 10 người lạm dụng hàng đầu của trang đăng nhập tới quản trị viên hệ thống với các đề xuất cấm mạng con tương ứng. ví dụ: Một IP đạt trang đăng nhập hơn 9000 lần. IP đó, và mạng con lớp D của nó hiện nằm trong danh sách đen. Tôi chắc rằng chúng tôi có thể tự động hóa điều này, mặc dù đó là một câu hỏi khác.
j0h

Câu trả lời:


13

Bạn có thể sử dụng grepuniqcho danh sách địa chỉ, lặp lại chúng và grepđếm lại cho số đếm:

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

grep -o '^[^ ]*'xuất ra mỗi ký tự từ đầu ( ^) cho đến khoảng trắng đầu tiên của mỗi dòng, uniqloại bỏ các dòng lặp lại, do đó để lại cho bạn một danh sách các địa chỉ IP. Nhờ thay thế lệnh, các forvòng lặp trong danh sách này in IP hiện đang được xử lý, theo sau là Số đếm và số đếm. Cái sau được tính bằng grep -c, trong đó đếm số dòng có ít nhất một trận đấu.

Chạy ví dụ

$ 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
Giải pháp này lặp đi lặp lại qua tệp đầu vào, một lần cho mỗi địa chỉ IP, sẽ rất chậm nếu tệp lớn. Các giải pháp khác sử dụng uniq -choặc awkchỉ cần đọc tệp một lần,
David

1
@David điều này là đúng, nhưng đây cũng là lần đầu tiên tôi đến với nó, vì biết rằng grep tính. Trừ khi hiệu suất có thể là một vấn đề ... không tối ưu hóa sớm?
D. Ben Knoble

3
Tôi sẽ không gọi nó là tối ưu hóa sớm, vì giải pháp hiệu quả hơn cũng đơn giản hơn, nhưng với mỗi giải pháp của riêng họ.
David

Nhân tiện, tại sao nó được viết như <log grep ...và không grep ... log?
Santiago

@Santiago Bởi vì đó là tốt hơn bằng nhiều cách, như Stéphane Chazelas giải thích ở đây trên U & L .
tráng miệng

39

Bạn có thể sử dụng cutuniqcác công cụ:

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

Giải trình :

  • cut -d ' ' -f1 : trích xuất trường đầu tiên (địa chỉ IP)
  • uniq -c : báo cáo các dòng lặp lại và hiển thị số lần xuất hiện

6
Người ta có thể sử dụng sed, ví dụ sed -E 's/ *(\S*) *(\S*)/\2 count: \1/'để có được đầu ra chính xác như OP muốn.
tráng miệng

2
Đây phải là câu trả lời được chấp nhận, vì một trong những món tráng miệng cần phải đọc các tập tin nhiều lần nên chậm hơn nhiều. Và bạn có thể dễ dàng sử dụng sort file | cut .... trong trường hợp bạn không chắc tệp đã được sắp xếp chưa.
Guntram Blohm hỗ trợ Monica

14

Nếu bạn không đặc biệt yêu cầu định dạng đầu ra nhất định, thì tôi sẽ đề xuất câu trả lời đã được đăng cut+ đã uniqdựa

Nếu bạn thực sự cần định dạng đầu ra nhất định, một cách duy nhất để thực hiện trong Awk sẽ là

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

Điều này hơi không lý tưởng khi đầu vào đã được sắp xếp vì nó không cần thiết lưu trữ tất cả IP vào bộ nhớ - một cách tốt hơn, mặc dù phức tạp hơn, trong trường hợp được sắp xếp trước (tương đương trực tiếp hơn uniq -c) sẽ là:

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

Vd

$ 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

thật dễ dàng để thay đổi câu trả lời dựa trên cut + uniq với sed xuất hiện ở định dạng yêu cầu.
Peter - Tái lập Monica

@ PeterA.Schneider đúng vậy - Tôi tin rằng điều đó đã được chỉ ra trong các bình luận cho câu trả lời đó
Steeldo

À, vâng, tôi hiểu rồi.
Peter - Tái lập Monica

8

Đây là một giải pháp khả thi:

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
  • thay thế file.logbằng tên tập tin thực tế.
  • biểu thức thay thế lệnh $(awk '{print $1}' "$IN_FILE" | sort -u)sẽ cung cấp một danh sách các giá trị duy nhất của cột đầu tiên.
  • sau đó grep -csẽ tính từng giá trị trong tệp.

$ 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
Thích printf...
D. Ben Knoble

1
Điều này có nghĩa là bạn cần xử lý toàn bộ tệp nhiều lần. Một lần để có được danh sách IP và sau đó một lần nữa cho mỗi IP bạn tìm thấy.
terdon

5

Một số 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

Đây là ý tưởng tương tự như cách tiếp cận awk của Steeldo , nhưng ở Perl. Các -anguyên nhân perl để tự động chia mỗi dòng đầu vào cho mảng @F, mà đầu tiên yếu tố (IP) là $F[0]. Vì vậy, $k{$F[0]}++sẽ tạo ra hàm băm %k, có khóa là IP và có giá trị là số lần mỗi IP được nhìn thấy. Đây }{là một perlspeak thú vị cho "làm phần còn lại ở cuối, sau khi xử lý tất cả đầu vào". Vì vậy, ở cuối, tập lệnh sẽ lặp qua các khóa của hàm băm và in khóa hiện tại ( $_) cùng với giá trị của nó ( $k{$_}).

Và, để mọi người không nghĩ rằng perl buộc bạn phải viết kịch bản trông giống như những nét vẽ nguệch ngoạc, đây là điều tương tự ở dạng ít cô đọng hơn:

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

Có lẽ đây không phải là điều OP muốn; tuy nhiên, nếu chúng ta biết rằng độ dài địa chỉ IP sẽ bị giới hạn ở 15 ký tự, một cách nhanh hơn để hiển thị số lượng với các IP duy nhất từ ​​một tệp nhật ký khổng lồ có thể đạt được uniqchỉ bằng cách sử dụng lệnh:

$ 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] ...

Tùy chọn:

-w Nso sánh không quá Nký tự trong dòng

-c sẽ tiền tố dòng theo số lần xuất hiện

Ngoài ra, đối với đầu ra được định dạng chính xác tôi thích awk(cũng nên hoạt động cho các địa chỉ 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

Lưu ý rằng uniqsẽ không phát hiện các dòng lặp lại trong tệp đầu vào nếu chúng không liền kề, do đó có thể cần thiết cho sorttệp.


1
Có khả năng đủ tốt trong thực tế, nhưng đáng chú ý các trường hợp góc. Chỉ có 6 ký tự có thể không đổi sau IP `- - [`. Nhưng trên lý thuyết, địa chỉ có thể ngắn hơn tối đa 8 ký tự, do đó, việc thay đổi ngày có thể chia số đếm cho một IP như vậy. Và như bạn gợi ý, điều này sẽ không hoạt động đối với IPv6.
Martin Thornton

Tôi thích nó, tôi không biết uniq có thể đếm!
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))

Đầu ra:

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

Giải thích: Lấy trường đầu tiên của my.log tách trên dấu gạch ngang -và sắp xếp nó. uniqcần sắp xếp đầu vào. -cnói với nó để đếm sự xuất hiện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.