Grep Abbina ed estrai


10

Ho un file che contiene linee come

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

Ho bisogno di estrarre il valore di proto, che è tcp/http, tcp/https, udp/dns.

Finora ho provato questo, grep -o 'proto=[^/]*/'ma solo in grado di estrarre il valore come proto=tcp/.



Questo è un lavoro per sed, awko perlno grep.
OrangeDog,

Risposte:


1

Supponendo che questo sia correlato alla tua domanda precedente , stai andando sulla strada sbagliata. Invece di provare a mettere insieme pezzi di script che faranno un po '/ sorta di fare quello che vuoi la maggior parte del tempo e che necessitano di ottenere uno script completamente diverso ogni volta che devi fare qualcosa di leggermente diverso, basta creare 1 script che può analizzare il tuo file di input in un array ( f[]sotto) che associa i nomi dei campi (tag) ai loro valori e quindi puoi fare quello che vuoi con il risultato, ad esempio dato questo file di input dalla tua domanda precedente:

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

possiamo scrivere uno script awk che crea una matrice dei valori indicizzati dai loro nomi / tag:

$ 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
}

e dato che puoi fare quello che ti piace con i tuoi dati facendo semplicemente riferimento ad essi con i nomi dei campi, ad esempio usando GNU awk -eper facilitare la miscelazione di uno script in un file con uno script da riga di comando:

$ 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
Questo è fantastico, grazie mille :)
user356831

Per questo tipo di lavoro, perlpuò essere più facile da usare.
OrangeDog,

1
@OrangeDog perché lo pensi? Mi piacerebbe davvero vedere l'equivalente in perl se non ti dispiace pubblicare una risposta del genere. Perl sicuramente non sarà più facile da usare se non ce l'ho sulla mia scatola e non riesco a installarlo, che è qualcosa che ho dovuto affrontare spesso nel corso degli anni. Awk d'altra parte è un'utilità obbligatoria e quindi è sempre presente nelle installazioni UNIX, proprio come sed, grep, sort, ecc.
Ed Morton,

@EdMorton vero, anche se non ho mai incontrato personalmente una distribuzione in cui perl non era incluso di default. Complessi awke sedscript sono di solito più semplici perlperché sono essenzialmente un superset di essi, con funzionalità aggiuntive per attività comuni.
OrangeDog,

@OrangeDog nessuno dovrebbe mai scrivere uno script sed più complicato di s/old/new/ge sed non è imbarazzante, quindi lasciamo perdere. Non sono assolutamente d'accordo sul fatto che script per awk complessi siano più semplici in perl. Possono essere più brevi, naturalmente, ma la brevità non è un attributo desiderabile del software, lo è la concisione, ed è estremamente raro per loro avere dei veri benefici, inoltre di solito sono molto più difficili da leggere, motivo per cui le persone pubblicano cose come zoitz.com / archives / 13 su perl e si riferiscono ad esso come un linguaggio di sola scrittura, a differenza di awk. Mi piacerebbe comunque vedere un perl equivalente a questo però
Ed Morton il

13

Con grep -o, dovrai abbinare esattamente ciò che vuoi estrarre. Dato che non vuoi estrarre la proto=stringa, non dovresti abbinarla.

È un'espressione regolare estesa che corrisponderebbe tcpo udpseguita da una barra e una stringa alfanumerica non vuota

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

Applicando questo sui tuoi dati:

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

Per assicurarci di farlo solo su righe che iniziano con la stringa proto=:

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

Con sed, rimuovendo tutto prima del primo =e dopo il primo carattere vuoto:

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

Per assicurarci che lo facciamo solo su righe che iniziano con la stringa proto=, puoi inserire la stessa fase di pre-elaborazione grepcome sopra, oppure puoi usare

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

Qui, sopprimiamo l'output predefinito con l' -nopzione, quindi attiviamo le sostituzioni e una stampa esplicita della riga solo se la riga corrisponde ^proto=.


Con awk, utilizzando il separatore di campo predefinito, quindi suddividere il primo campo =e stamparne il secondo bit:

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

Per assicurarci che lo facciamo solo su righe che iniziano con la stringa proto=, puoi inserire la stessa fase di pre-elaborazione grepcome sopra, oppure puoi usare

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

10

Se sei su GNU grep (per l' -Popzione), puoi usare:

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

Qui abbiniamo la proto=stringa, per assicurarci di estrarre la colonna corretta, ma poi la scartiamo dall'output con il \Kflag.

Quanto sopra presuppone che le colonne siano separate da spazio. Se le schede sono anche un separatore valido, dovresti usare \Sper abbinare i caratteri non bianchi, quindi il comando sarebbe:

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

Se si desidera proteggere anche dai campi di corrispondenza in cui proto=è presente una sottostringa, ad esempio a thisisnotaproto=tcp/https, è possibile aggiungere un limite di parole con in questo \bmodo:

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

1
Puoi migliorarlo scrivendo solo grep -oP 'proto=\K\S+'. La proto=tcp/httppuò essere seguita da una scheda anziché spazi, e \Sdiversamente da [^ ]ogni carattere non-spazio.
mosvy,

@mosvy: è un buon suggerimento, grazie.
user000001

1
Comunque, -oè anche un GNUismo. -Pè supportato da GNU solo grepse costruito con il supporto PCRE (opzionale al momento della compilazione).
Stéphane Chazelas,

6

Utilizzando awk:

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

$1 ~ "proto"ci assicureremo che agiamo solo sulle linee con protonella prima colonna

sub(/proto=/, "")rimuoverà proto=dall'input

print $1 stampa la colonna rimanente


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

3

Codice golf sulle grepsoluzioni

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

o anche

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


2

Solo un'altra grepsoluzione:

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

E uno simile con la sedstampa solo del gruppo acquisito abbinato:

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

1

Un altro awkapproccio:

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

Ciò imposterà il separatore di campo di Awk su uno =o su uno spazio. Quindi, se la riga corrisponde a =, quindi a udo tcseguita da a p, stampa il 2 ° campo.

Un altro sedapproccio (non portatile per tutte le versioni di sed, ma funziona con GNU sed):

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

Il -nmezzo "non stampare" e -Eabilita espressioni regolari estese che ci danno \Sper "non-spazi", +per "uno o più" e le parentesi per la cattura. Alla fine, /palla fine, la stampa di sed verrà stampata una riga solo se l'operazione ha avuto esito positivo, quindi se c'è stata una corrispondenza per l'operatore di sostituzione.

E, perl uno:

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

Il -nmezzo "legge il file di input riga per riga e applica lo script fornito da -eciascuna riga". Il -laggiunge una nuova riga ad ogni printchiamata (e rimuove nuove righe in uscita dall'input). Lo script stesso stamperà il tratto più lungo di caratteri non bianchi trovati dopo a proto=.


1
-Esta diventando sempre più portatile, ma \Snon lo è. [^[:space:]]è un equivalente più portatile.
Stéphane Chazelas,

1

Ecco un'altra soluzione abbastanza semplice:

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

Il tuo grepnon corrisponde a nulla. [tc,ud]\*\\/.*look per uno verificarsi di uno to co ,o uo d, seguiti da un letterale *carattere, poi una pe una barra rovesciata. Probabilmente intendevi grep -Eo '(tc|ud)p/.* ' file | awk '{print $1}'. Ma allora, se si sta utilizzando awk, si può anche fare il tutto in awk: awk -F'[= ]' '/(tc|ud)p/{print $2}' file.
terdon

Qualcuno ha modificato il mio originale, c'era una barra rovesciata prima della stella, che ho appena rimosso, signore.
mkzia,

Grazie per il montaggio, ma temo che funzioni solo per caso. Come ho spiegato prima, [tc,ud]psignifica "uno dei t, c, ,, uo dseguita da una p. Quindi partite qui solo perché tcpha cpe udpha dp. Ma sarebbe anche corrispondere ,po tpecc Inoltre, ora che avete la *, si abbinerà ppppure (il *significa "0 o più", quindi corrisponderà anche quando non corrisponde). Non vuoi una classe di caratteri ( [ ]), quello che vuoi è un gruppo: (tc|ud)(usa con la -Ebandiera di grep). Inoltre, lo .*rende corrisponde a tutta la riga
terdon

1
@Jesse_b: Sebbene mkzia non sia tecnicamente un "Nuovo collaboratore", sono un utente inesperto, come dimostra il fatto che non hanno usato la formattazione del codice per il loro comando. Eppure erano abbastanza intelligenti da digitare \*per far *apparire il primo nel loro comando come * e non come corsivo. Quando si inserisce il comando in formato codice, viene visualizzato \prima il *simbolo (in tal modo il comando non riesce). Quando modifichi i post di altre persone, fai attenzione a cambiare l'aspetto del post in questo modo.
G-Man dice "Ripristina Monica" l'

@terdon: (1) No, in realtà non corrisponderà ppp. Naturalmente hai ragione che corrisponderà ,po  tp- o uucp, ttp, cutp, ductpo d,up.
G-Man dice "Ripristina Monica" l'


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

opzioni di taglio:

  • -f - campo
  • -d - delimetro
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.