Grep Match und extrahieren


10

Ich habe eine Datei, die Zeilen enthält als

proto=tcp/http  sent=144        rcvd=52 spkt=3 
proto=tcp/https  sent=145        rcvd=52 spkt=3
proto=udp/dns  sent=144        rcvd=52 spkt=3

Ich brauche den Wert von Proto zu extrahieren , das ist tcp/http, tcp/https, udp/dns.

Bisher habe ich dies versucht grep -o 'proto=[^/]*/', konnte aber nur den Wert als extrahieren proto=tcp/.



Dies ist eine Aufgabe für sed, awkoder perlnicht grep.
OrangeDog

Antworten:


1

Angenommen, dies hängt mit Ihrer vorherigen Frage zusammen , gehen Sie den falschen Weg. Anstatt zu versuchen, Teile von Skripten zusammenzusetzen, die die meiste Zeit irgendwie das tun, was Sie wollen, und jedes Mal ein völlig anderes Skript benötigen, wenn Sie etwas anderes tun müssen, erstellen Sie einfach 1 Skript, das Ihre analysieren kann Geben Sie die Datei in ein Array ( f[]unten) ein, das Ihre Feldnamen (Tags) ihren Werten zuordnet. Anschließend können Sie mit dem Ergebnis alles tun, was Sie möchten, z. B. anhand dieser Eingabedatei aus Ihrer vorherigen Frage:

$ cat file
Feb             3       0:18:51 17.1.1.1                      id=firewall     sn=qasasdasd "time=""2018-02-03"     22:47:55        "UTC""" fw=111.111.111.111       pri=6    c=2644        m=88    "msg=""Connection"      "Opened"""      app=2   n=2437       src=12.1.1.11:49894:X0       dst=4.2.2.2:53:X1       dstMac=42:16:1b:af:8e:e1        proto=udp/dns   sent=83 "rule=""5"      "(LAN->WAN)"""

Wir können ein awk-Skript schreiben, das ein Array der Werte erstellt, die durch ihre Namen / Tags indiziert sind:

$ cat tst.awk
{
    f["hdDate"] = $1 " " $2
    f["hdTime"] = $3
    f["hdIp"]   = $4
    sub(/^([^[:space:]]+[[:space:]]+){4}/,"")

    while ( match($0,/[^[:space:]]+="?/) ) {
        if ( tag != "" ) {
            val = substr($0,1,RSTART-1)
            gsub(/^[[:space:]]+|("")?[[:space:]]*$/,"",val)
            f[tag] = val
        }

        tag = substr($0,RSTART,RLENGTH-1)
        gsub(/^"|="?$/,"",tag)

        $0 = substr($0,RSTART+RLENGTH)
    }

    val = $0
    gsub(/^[[:space:]]+|("")?[[:space:]]*$/,"",val)
    f[tag] = val
}

und vorausgesetzt, Sie können mit Ihren Daten tun, was Sie möchten, indem Sie sie nur anhand der Feldnamen referenzieren, z. B. mit GNU awk, -eum das Mischen eines Skripts in einer Datei mit einem Befehlszeilenskript zu vereinfachen:

$ awk -f tst.awk -e '{for (tag in f) printf "f[%s]=%s\n", tag, f[tag]}' file
f[fw]=111.111.111.111
f[dst]=4.2.2.2:53:X1
f[sn]=qasasdasd
f[hdTime]=0:18:51
f[sent]=83
f[m]=88
f[hdDate]=Feb 3
f[n]=2437
f[app]=2
f[hdIp]=17.1.1.1
f[src]=12.1.1.11:49894:X0
f[c]=2644
f[dstMac]=42:16:1b:af:8e:e1
f[msg]="Connection"      "Opened"
f[rule]="5"      "(LAN->WAN)"
f[proto]=udp/dns
f[id]=firewall
f[time]="2018-02-03"     22:47:55        "UTC"
f[pri]=6

$ awk -f tst.awk -e '{print f["proto"]}' file
udp/dns

$ awk -f tst.awk -e 'f["proto"] ~ /udp/ {print f["sent"], f["src"]}' file
83 12.1.1.11:49894:X0

2
Das ist großartig, vielen Dank :)
user356831

Für diese Art von Arbeit perlkann es einfacher sein, sie zu verwenden.
OrangeDog

1
@OrangeDog warum denkst du das? Ich würde das Äquivalent gerne in Perl sehen, wenn es Ihnen nichts ausmacht, eine solche Antwort zu veröffentlichen. Perl wird definitiv nicht einfacher zu bedienen sein, wenn ich es nicht auf meiner Box habe und es nicht installieren kann, was ich im Laufe der Jahre häufig zu tun hatte. Awk hingegen ist ein obligatorisches Dienstprogramm und daher bei UNIX-Installationen immer vorhanden, genau wie sed, grep, sort usw.
Ed Morton

@EdMorton true, obwohl ich persönlich noch nie auf eine Distribution gestoßen bin, bei der Perl standardmäßig nicht enthalten war. Komplexe awkund sedSkripte sind in der Regel einfacher, perlda sie im Wesentlichen eine Obermenge von ihnen sind und zusätzliche Funktionen für allgemeine Aufgaben bieten.
OrangeDog

@OrangeDog Niemand sollte jemals ein sed-Skript schreiben, das komplizierter ist als s/old/new/gund sed ist nicht awk, also lassen Sie uns das beiseite legen. Ich bin völlig anderer Meinung, dass komplexe awk-Skripte in Perl einfacher sind. Sie können natürlich kürzer sein, aber Kürze ist kein wünschenswertes Merkmal von Software, Prägnanz ist es, und es ist äußerst selten, dass sie einen wirklichen Nutzen haben, und sie sind normalerweise weitaus schwieriger zu lesen, weshalb Leute Dinge wie zoitz.com posten / archives / 13 über Perl und bezeichnen es im Gegensatz zu awk als reine Schreibsprache. Ich würde immer noch gerne ein Perl-Äquivalent dazu sehen
Ed Morton

13

Mit grep -omüssen Sie genau das finden, was Sie extrahieren möchten. Da Sie die proto=Zeichenfolge nicht extrahieren möchten , sollten Sie sie nicht abgleichen.

Ein erweiterter regulärer Ausdruck, der entweder mit einem Schrägstrich oder einer nicht leeren alphanumerischen Zeichenfolge übereinstimmt tcpoder darauf udpfolgt, lautet

(tcp|udp)/[[:alnum:]]+

Dies auf Ihre Daten anwenden:

$ grep -E -o '(tcp|udp)/[[:alnum:]]+' file
tcp/http
tcp/https
udp/dns

Um sicherzustellen, dass wir dies nur in Zeilen tun, die mit der Zeichenfolge beginnen proto=:

grep '^proto=' file | grep -E -o '(tcp|udp)/[[:alnum:]]+'

Mit sed, alles vor dem ersten =und nach dem ersten Leerzeichen entfernen :

$ sed 's/^[^=]*=//; s/[[:blank:]].*//' file
tcp/http
tcp/https
udp/dns

Um sicherzustellen, dass wir dies nur in Zeilen tun, die mit der Zeichenfolge beginnen proto=, können Sie denselben Vorverarbeitungsschritt grepwie oben einfügen oder verwenden

sed -n '/^proto=/{ s/^[^=]*=//; s/[[:blank:]].*//; p; }' file

Hier unterdrücken wir die Standardausgabe mit der -nOption und lösen dann die Ersetzungen und einen expliziten Ausdruck der Zeile nur dann aus, wenn die Zeile übereinstimmt ^proto=.


Mit awkden Standardfeldtrennzeichen verwendet wird , und dann die erste Feldaufspaltung auf , =und Drucken des zweiten Stück davon:

$ awk '{ split($1, a, "="); print a[2] }' file
tcp/http
tcp/https
udp/dns

Um sicherzustellen, dass wir dies nur in Zeilen tun, die mit der Zeichenfolge beginnen proto=, können Sie denselben Vorverarbeitungsschritt grepwie oben einfügen oder verwenden

awk '/^proto=/ { split($1, a, "="); print a[2] }' file

10

Wenn Sie sich auf GNU grep befinden (für die -POption), können Sie Folgendes verwenden:

$ grep -oP 'proto=\K[^ ]*' file
tcp/http
tcp/https
udp/dns

Hier stimmen wir mit der proto=Zeichenfolge überein , um sicherzustellen, dass wir die richtige Spalte extrahieren, aber dann verwerfen wir sie mit dem \KFlag aus der Ausgabe .

Das Obige setzt voraus, dass die Spalten durch Leerzeichen getrennt sind. Wenn Tabulatoren auch ein gültiges Trennzeichen sind, würden Sie \Sdie Nicht-Leerzeichen verwenden, sodass der Befehl wie folgt lautet:

grep -oP 'proto=\K\S*' file

Wenn Sie auch vor Übereinstimmungsfeldern schützen möchten, in denen proto=sich eine Teilzeichenfolge befindet, z. B. a thisisnotaproto=tcp/https, können Sie eine Wortgrenze mit folgender Adresse hinzufügen \b:

grep -oP '\bproto=\K\S*' file

1
Sie können das verbessern, indem Sie einfach schreiben grep -oP 'proto=\K\S+'. Dem proto=tcp/httpkann anstelle von Leerzeichen eine Registerkarte folgen, die im \SGegensatz [^ ]zu allen Nicht-Leerzeichen übereinstimmt.
Mosvy

@ Mosvy: Das ist ein guter Vorschlag, danke.
user000001

1
Wie auch immer, -oist auch ein GNUismus. -Pwird von GNU nur unterstützt, grepwenn es mit PCRE-Unterstützung erstellt wurde (optional zur Erstellungszeit).
Stéphane Chazelas

6

Verwenden von awk:

awk '$1 ~ "proto" { sub(/proto=/, ""); print $1 }' input

$1 ~ "proto"wird sicherstellen, dass wir nur in Zeilen mit protoin der ersten Spalte handeln

sub(/proto=/, "")wird proto=von der Eingabe entfernt

print $1 druckt die verbleibende Spalte


$ awk '$1 ~ "proto" { sub(/proto=/, ""); print $1 }' input
tcp/http
tcp/https
udp/dns

3

Code Golf auf den grepLösungen

grep -Po "..p/[^ ]+" file

oder auch

grep -Po "..p/\S+" file


2

Nur eine andere grepLösung:

grep -o '[^=/]\+/[^ ]\+' file

Und eine ähnliche, bei der sednur die übereinstimmende erfasste Gruppe gedruckt wird:

sed -n 's/.*=\([^/]\+\/[^ ]\+\).*/\1/p' file

1

Ein anderer awkAnsatz:

$ awk -F'[= ]' '/=(tc|ud)p/{print $2}' file
tcp/http
tcp/https
udp/dns

Dadurch wird das Feldtrennzeichen von awk auf entweder =oder ein Leerzeichen gesetzt. Wenn die Zeile mit a übereinstimmt , drucken Sie =entweder udoder tcgefolgt von a pdas 2. Feld.

Ein anderer sedAnsatz (nicht für alle Versionen von sedGNU portierbar , funktioniert aber mit GNU sed):

$ sed -En 's/^proto=(\S+).*/\1/p' file 
tcp/http
tcp/https
udp/dns

Das -nbedeutet "nicht drucken" und -Eaktiviert erweiterte reguläre Ausdrücke, die uns \Sfür "Nicht-Leerzeichen", +für "ein oder mehrere" und die Klammern für die Erfassung geben. Schließlich /pwird sed am Ende nur dann eine Zeile drucken, wenn der Vorgang erfolgreich war, wenn also eine Übereinstimmung für den Substitutionsoperator vorliegt.

Und ein Perl:

$ perl -nle '/^proto=(\S+)/ && print $1' file 
tcp/http
tcp/https
udp/dns

Das -nbedeutet "Lesen Sie die Eingabedatei Zeile für Zeile und wenden Sie das von -ejeder Zeile angegebene Skript an". Das -lfügt jedem printAnruf eine neue Zeile hinzu (und entfernt bestehende Zeilen aus der Eingabe). Das Skript selbst druckt die längste Strecke von Nicht-Leerzeichen nach a proto=.


1
-Ewird immer portabler, ist es aber \Snicht. [^[:space:]]ist ein tragbareres Äquivalent.
Stéphane Chazelas

1

Hier ist eine andere Lösung ganz einfach:

grep -o "[tc,ud]*p\\/.*  "   INPUTFile.txt  |   awk '{print $1}'

Dein greppasst zu nichts. [tc,ud]\*\\/.*sucht nach einem Vorkommen von entweder toder coder ,oder uoder d, gefolgt von einem wörtlichen *Zeichen, dann einem pund einem Backslash. Du hast es wahrscheinlich gemeint grep -Eo '(tc|ud)p/.* ' file | awk '{print $1}'. Aber wenn Sie awk verwenden, können Sie das Ganze auch in awk ausführen : awk -F'[= ]' '/(tc|ud)p/{print $2}' file.
Terdon

Jemand hat mein Original modifiziert, es gab einen zusätzlichen Backslash vor dem Stern, den ich gerade entfernt habe, Sir.
Mkzia

Danke für die Bearbeitung, aber ich fürchte, das funktioniert nur zufällig. Wie ich bereits erklärt, [tc,ud]pbedeutet „eine der t, c, ,, uoder ddurch eine gefolgt p. So ist es hier nur Spiele , da tcphat cpund udphat dp. Es wäre aber auch passen ,poder tpauch usw., jetzt , dass Sie das haben *, wird es passen pppauch (die *bedeutet "0 oder mehr", damit es auch dann übereinstimmt, wenn es nicht übereinstimmt. Sie möchten keine Zeichenklasse ( [ ]), was Sie möchten, ist eine Gruppe: (tc|ud)(mit dem -EFlag von verwenden grep). Auch das .*macht es passen Sie die gesamte Linie an.
terdon

1
@Jesse_b: Obwohl mkzia technisch gesehen kein „neuer Mitwirkender“ ist, sind sie ein unerfahrener Benutzer, was durch die Tatsache belegt wird, dass sie für ihren Befehl keine Code-Formatierung verwendet haben. Und doch waren sie klug genug, um zu tippen \*, damit die ersten *in ihrem Befehl als * und nicht als kursiv markiert wurden. Wenn Sie den Befehl in das Codeformat versetzen, wird das \vor dem *Erscheinen angezeigt (wodurch der Befehl fehlschlägt). Wenn Sie die Beiträge anderer Personen bearbeiten, achten Sie bitte darauf, das Erscheinungsbild des Beitrags wie folgt zu ändern.
G-Man sagt 'Reinstate Monica'

@terdon: (1) Nein, eigentlich passt es nicht zusammen ppp. Natürlich haben Sie Recht , dass es passen ,poder  tp- oder uucp, ttp, cutp, ductpoder d,up.
G-Man sagt 'Reinstate Monica'


0
cat file| cut -f1 -d' '| cut -f2 -d'='
tcp/http
tcp/https
udp/dns

Schnittoptionen:

  • -f - Feld
  • -d - Begrenzer
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.