Wie zähle ich Textvorkommen in einer Datei?


19

Ich habe eine Protokolldatei, die nach IP-Adressen sortiert ist. Ich möchte die Anzahl der Vorkommen jeder eindeutigen IP-Adresse ermitteln. Wie kann ich das mit bash machen? Möglicherweise wird die Anzahl der Vorkommen neben einer IP-Adresse aufgeführt, z. B .:

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

und so weiter.

Hier ist ein Beispiel des Protokolls:

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
Meinen Sie mit "bash" die einfache Shell oder die Befehlszeile im Allgemeinen?
Dessert

1
Haben Sie eine Datenbanksoftware zur Verfügung?
SpacePhoenix


Das Protokoll stammt von einem Appache2-Server, nicht wirklich von einer Datenbank. bash ist das, was ich im allgemeinen Anwendungsfall vorziehen würde. Ich sehe die Python- und Perl-Lösungen, wenn sie für jemand anderen gut sind, ist das großartig. Die anfängliche Sortierung wurde durchgeführt, sort -Vobwohl ich denke, dass dies nicht erforderlich war. Ich habe die 10 häufigsten Nutzer der Anmeldeseite mit Empfehlungen zum Sperren der jeweiligen Subnetze an den Systemadministrator gesendet. Beispielsweise hat eine IP die Anmeldeseite mehr als 9000 Mal aufgerufen. Diese IP und ihr Subnetz der Klasse D sind jetzt auf der schwarzen Liste. Ich bin sicher, wir könnten das automatisieren, obwohl das eine andere Frage ist.
31.

Antworten:


13

Sie können grepund uniqfür die Liste der Adressen verwenden und diese immer grepwieder für die Zählung durchlaufen:

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

grep -o '^[^ ]*'Gibt jedes Zeichen von Anfang an ( ^) bis zum ersten Leerzeichen jeder Zeile aus, uniqentfernt wiederholte Zeilen und hinterlässt so eine Liste mit IP-Adressen. Dank der Befehlssubstitution durchläuft die forSchleife diese Liste und druckt die aktuell verarbeitete IP, gefolgt von „count“ und der Anzahl. Letzteres wird mit berechnet grep -c, wobei die Anzahl der Zeilen mit mindestens einer Übereinstimmung gezählt wird.

Beispiellauf

$ 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
Diese Lösung durchläuft die Eingabedatei wiederholt, und zwar einmal für jede IP-Adresse. Dies ist sehr langsam, wenn die Datei groß ist. Die anderen Lösungen verwenden uniq -coder awkmüssen die Datei nur einmal lesen,
David

1
@ David das ist wahr, aber das wäre auch mein erster Versuch gewesen, zu wissen, dass grep zählt. Es sei denn, Leistung ist messbar ein Problem ... Nicht vorzeitig optimieren?
D. Ben Knoble

3
Ich würde es nicht als vorzeitige Optimierung bezeichnen, da die effizientere Lösung auch einfacher ist, sondern für jeden das Richtige.
David

Übrigens, warum steht es so <log grep ...und nicht so grep ... log?
Santiago,

@Santiago Weil das in vielerlei Hinsicht besser ist, wie Stéphane Chazelas hier auf U & L erklärt .
Dessert

39

Sie können cutund uniqWerkzeuge verwenden:

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

Erklärung:

  • cut -d ' ' -f1 : Erstes Feld extrahieren (IP-Adresse)
  • uniq -c : Wiederholte Zeilen melden und Anzahl der Vorkommen anzeigen

6
Man könnte sedzB verwenden sed -E 's/ *(\S*) *(\S*)/\2 count: \1/', um die Ausgabe genau so zu bekommen, wie OP es wollte.
Dessert

2
Dies sollte die akzeptierte Antwort sein, da die Antwort beim Nachtisch wiederholt gelesen werden muss und daher viel langsamer ist. Und Sie können es ganz einfach verwenden sort file | cut .... , wenn Sie nicht sicher sind, ob die Datei bereits sortiert ist.
Guntram Blohm unterstützt Monica

14

Wenn Sie das angegebene Ausgabeformat nicht speziell benötigen, würde ich die bereits gepostete + Antwort empfehlencutuniq

Wenn Sie das angegebene Ausgabeformat wirklich benötigen, ist dies in Awk in einem Durchgang möglich

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

Dies ist nicht ideal, wenn die Eingabe bereits sortiert ist, da unnötigerweise alle IP-Adressen im Speicher gespeichert werden - eine bessere, wenn auch kompliziertere Möglichkeit, dies im vorsortierten Fall zu tun (direkter äquivalent zu uniq -c), wäre:

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

Es wäre einfach, die cut + uniq-basierte Antwort so zu ändern, dass sie im gewünschten Format angezeigt wird.
Peter - Setzen Sie Monica wieder ein

@PeterA.Schneider ja es würde - ich glaube das wurde schon in Kommentaren zu dieser Antwort darauf hingewiesen
steeldriver

Ah ja, ich verstehe.
Peter - Reinstate Monica

8

Hier ist eine mögliche Lösung:

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
  • Ersetzen Sie file.logmit dem tatsächlichen Dateinamen.
  • Der Befehlsersetzungsausdruck $(awk '{print $1}' "$IN_FILE" | sort -u)liefert eine Liste der eindeutigen Werte der ersten Spalte.
  • Dann grep -cwird jeder dieser Werte in der Datei gezählt.

$ 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
Lieber printf...
D. Ben Knoble

1
Dies bedeutet, dass Sie die gesamte Datei mehrmals verarbeiten müssen. Einmal, um die Liste der IPs abzurufen, und dann noch einmal für jede der gefundenen IPs.
Terdon

5

Einige 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

Dies ist die gleiche Idee wie Steeldrivers awk-Ansatz , jedoch in Perl. Das -abewirkt, dass Perl jede Eingabezeile automatisch in das Array aufteilt @F, dessen erstes Element (die IP) ist $F[0]. Also $k{$F[0]}++wird der Hash erstellt %k, dessen Schlüssel die IPs sind und dessen Werte die Häufigkeit sind, mit der jede IP gesehen wurde. Das }{ist funky perlspeak für "Mach den Rest am Ende, nachdem du alle Eingaben verarbeitet hast". Am Ende durchläuft das Skript die Schlüssel des Hashs und gibt den aktuellen Schlüssel ( $_) zusammen mit seinem Wert ( $k{$_}) aus.

Und nur damit die Leute nicht glauben, dass Perl Sie zwingt, ein Skript zu schreiben, das wie kryptische Kritzeleien aussieht, ist dies in einer weniger komprimierten Form dasselbe:

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

Vielleicht ist es nicht das, was die OP wollen; Wenn wir jedoch wissen, dass die Länge der IP-Adresse auf 15 Zeichen begrenzt ist, können Sie die Anzahl der eindeutigen IP-Adressen aus einer großen Protokolldatei schneller anzeigen, indem Sie den uniqBefehl allein verwenden:

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

Optionen:

-w N vergleicht nicht mehr als N Zeichen in Zeilen

-c wird den Zeilen die Anzahl der Vorkommen voranstellen

Alternativ bevorzuge ich für eine exakt formatierte Ausgabe awk(sollte auch für IPV6-Adressen funktionieren) 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

Beachten Sie, dass uniqwiederholte Zeilen in der Eingabedatei nicht erkannt werden, wenn sie nicht benachbart sind. Daher kann dies für sortdie Datei erforderlich sein .


1
Wahrscheinlich gut genug in der Praxis, aber es lohnt sich, die Eckfälle zu beachten. Nur 6 wahrscheinlich konstante Zeichen nach der IP `- - [`. Theoretisch kann die Adresse jedoch bis zu 8 Zeichen kürzer als das Maximum sein, sodass eine Änderung des Datums die Anzahl für eine solche IP aufteilen kann. Und wie Sie andeuten, funktioniert dies bei IPv6 nicht.
Martin Thornton

Ich mag es, ich wusste nicht, dass uniq zählen kann!
31.

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))

Ausgabe:

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

Erläuterung: Nehmen Sie das erste Feld von my.log, das in Bindestriche aufgeteilt ist, -und sortieren Sie es. uniqbenötigt sortierte Eingabe. -csagt, es soll Vorkommen zählen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.