Bagaimana cara menghitung kemunculan teks dalam file?

19

Saya memiliki file log yang diurutkan berdasarkan alamat IP, saya ingin mencari jumlah kemunculan setiap alamat IP yang unik. Bagaimana saya bisa melakukan ini dengan bash? Kemungkinan daftar jumlah kejadian di sebelah IP, seperti:

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

dan seterusnya.

Berikut contoh log:

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"
j0h
sumber
1
Dengan "bash", maksud Anda shell biasa atau baris perintah secara umum?
hidangan penutup
1
Apakah Anda memiliki perangkat lunak database yang tersedia untuk digunakan?
SpacePhoenix
1
Terkait
Julien Lopez
Log berasal dari server appache2, bukan benar-benar database. bash adalah apa yang saya inginkan, dalam kasus penggunaan umum. Saya melihat solusi python dan perl, jika mereka bagus untuk orang lain, itu bagus. penyortiran awal dilakukan dengan sort -Vmeskipun saya pikir itu tidak diperlukan. Saya mengirim 10 pelaku penyalahgunaan halaman login ke admin sistem dengan rekomendasi untuk melarang subnet masing-masing. misalnya, One IP mencapai halaman login lebih dari 9000 kali. IP, & subnet kelas D-nya sekarang masuk daftar hitam. Saya yakin kami bisa mengotomatisasi ini, meskipun itu adalah pertanyaan yang berbeda.
j0h

Jawaban:

13

Anda dapat menggunakan grepdan uniquntuk daftar alamat, mengulanginya dan grepkembali menghitung:

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

grep -o '^[^ ]*'menampilkan setiap karakter dari awal ( ^) hingga spasi pertama dari setiap baris, uniqmenghilangkan baris yang diulang, sehingga meninggalkan Anda dengan daftar alamat IP. Berkat substitusi perintah, forloop loop atas daftar ini mencetak IP yang sedang diproses diikuti oleh "menghitung" dan menghitung. Yang terakhir dihitung dengan grep -c, yang menghitung jumlah garis dengan setidaknya satu kecocokan.

Contoh dijalankan

$ 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
pencuci mulut
sumber
13
Solusi ini berulang pada file input berulang, sekali untuk setiap alamat IP, yang akan sangat lambat jika file tersebut besar. Solusi lain menggunakan uniq -catau awkhanya perlu membaca file sekali,
David
1
@ Bersihkan ini benar, tetapi ini akan menjadi yang pertama saya lakukan juga, mengetahui bahwa grep diperhitungkan. Kecuali jika kinerja secara terukur merupakan masalah ... jangan optimalkan secara prematur?
D. Ben Knoble
3
Saya tidak akan menyebutnya optimasi prematur, mengingat bahwa solusi yang lebih efisien juga lebih sederhana, tetapi masing-masing untuk mereka sendiri.
David
Ngomong-ngomong, mengapa ini ditulis seperti <log grep ...dan tidak grep ... log?
Santiago
@Antiago Karena itu lebih baik dalam banyak hal, seperti dijelaskan oleh Stéphane Chazelas di U&L .
hidangan penutup
39

Anda dapat menggunakan cutdan uniqalat:

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

Penjelasan:

  • cut -d ' ' -f1 : ekstrak bidang pertama (alamat ip)
  • uniq -c : laporkan baris yang berulang dan tampilkan jumlah kemunculannya
Flora Mikael
sumber
6
Orang bisa menggunakan sed, misalnya sed -E 's/ *(\S*) *(\S*)/\2 count: \1/'untuk mendapatkan output persis seperti yang diinginkan OP.
hidangan penutup
2
Ini harus menjadi jawaban yang diterima, karena yang oleh pencuci mulut perlu membaca file berulang kali jadi jauh lebih lambat. Dan Anda dapat dengan mudah menggunakan sort file | cut .... jika Anda tidak yakin apakah file tersebut sudah diurutkan.
Guntram Blohm mendukung Monica
14

Jika Anda tidak secara khusus memerlukan format output yang diberikan, maka saya akan merekomendasikan jawaban yang sudah diposting cut+ uniqberbasis

Jika Anda benar - benar membutuhkan format output yang diberikan, cara single-pass untuk melakukannya dalam Awk adalah

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

Ini agak tidak ideal ketika input sudah disortir karena tidak perlu menyimpan semua IP ke dalam memori - cara yang lebih baik, meskipun lebih rumit, untuk melakukannya dalam case pra-sortir (lebih langsung setara dengan uniq -c) adalah:

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

Ex.

$ 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
Steeldriver
sumber
akan mudah untuk mengubah jawaban berdasarkan cut + uniq dengan sed untuk muncul dalam format yang diminta.
Peter - Pasang kembali Monica
@ PeterA.Schneider ya itu akan - saya percaya itu sudah ditunjukkan dalam komentar untuk jawaban itu
steeldriver
Ah, ya, saya mengerti.
Peter - Reinstate Monica
8

Inilah salah satu solusi yang mungkin:

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
  • ganti file.logdengan nama file yang sebenarnya.
  • ekspresi substitusi perintah $(awk '{print $1}' "$IN_FILE" | sort -u)akan memberikan daftar nilai unik dari kolom pertama.
  • kemudian grep -cakan menghitung masing-masing nilai ini di dalam file.

$ 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
pa4080
sumber
1
Lebih suka printf...
D. Ben Knoble
1
Ini berarti Anda perlu memproses seluruh file beberapa kali. Sekali untuk mendapatkan daftar IP dan sekali lagi untuk masing-masing IP yang Anda temukan.
terdon
5

Beberapa 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

Ini adalah ide yang sama dengan pendekatan awk Steeldriver , tetapi dalam Perl. The -apenyebab perl untuk secara otomatis membagi setiap baris masukan ke dalam array @F, yang pertama elemen (IP) adalah $F[0]. Jadi, $k{$F[0]}++akan membuat hash %k, yang kuncinya adalah IP dan yang nilainya berapa kali setiap IP terlihat. The }{adalah perlspeak funky "melakukan sisanya di akhir, setelah pengolahan semua masukan". Jadi, pada akhirnya, skrip akan beralih pada kunci hash dan mencetak kunci saat ini ( $_) bersama dengan nilainya ( $k{$_}).

Dan, supaya orang tidak berpikir perl memaksa Anda untuk menulis skrip yang terlihat seperti coretan samar, ini adalah hal yang sama dalam bentuk yang kurang kental:

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
terdon
sumber
4

Mungkin ini bukan yang diinginkan OP; namun, jika kita tahu bahwa panjang alamat IP akan dibatasi hingga 15 karakter, cara yang lebih cepat untuk menampilkan jumlah dengan IP unik dari file log besar dapat dicapai menggunakan uniqperintah saja:

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

Pilihan:

-w Nmembandingkan tidak lebih dari Nkarakter dalam baris

-c akan mengawali garis dengan jumlah kemunculan

Atau, Untuk output yang diformat dengan tepat saya lebih suka awk(juga harus bekerja untuk alamat 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

Catatan yang uniqtidak akan mendeteksi baris berulang di file input jika mereka tidak berdekatan, jadi mungkin perlu untuk sortfile tersebut.

Y. Pradhan
sumber
1
Agaknya cukup bagus dalam praktik, tetapi patut dicatat kasus sudut. Hanya 6 karakter yang mungkin konstan setelah IP `- - [`. Namun secara teori, alamat bisa lebih pendek hingga 8 karakter dari maksimum sehingga perubahan tanggal dapat membagi jumlah IP yang demikian. Dan seperti yang Anda beri petunjuk, ini tidak akan berfungsi untuk IPv6.
Martin Thornton
Saya suka, saya tidak tahu uniq bisa menghitung!
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))

Keluaran:

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
wjandrea
sumber
0
cut -f1 -d- my.log | sort | uniq -c

Penjelasan: Ambil bidang pertama dari pemisahan my.log pada tanda hubung -dan urutkan. uniqmembutuhkan input yang diurutkan. -cmengatakannya untuk menghitung kejadian.

PhD
sumber