Grep Match et extrait

10

J'ai un fichier qui contient des lignes comme

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

Je dois extraire la valeur de proto qui est tcp/http, tcp/https, udp/dns.

Jusqu'à présent, j'ai essayé cela, grep -o 'proto=[^/]*/'mais je n'ai pu extraire la valeur que proto=tcp/.

user356831
la source
Ceci est un travail pour sed, awkou perlnon grep.
OrangeDog

Réponses:

1

En supposant que cela est lié à votre question précédente , vous vous trompez de chemin. Plutôt que d'essayer de reconstituer des morceaux de scripts qui feront un peu / sorta ce que vous voulez la plupart du temps et ayant besoin d'obtenir un script complètement différent à chaque fois que vous avez besoin de faire quelque chose de légèrement différent, créez simplement 1 script qui peut analyser votre fichier d'entrée dans un tableau ( f[]ci-dessous) qui mappe les noms de vos champs (balises) à leurs valeurs, puis vous pouvez faire tout ce que vous voulez avec le résultat, par exemple, étant donné ce fichier d'entrée de votre question précédente:

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

nous pouvons écrire un script awk qui crée un tableau des valeurs indexées par leurs noms / balises:

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

et étant donné que vous pouvez faire ce que vous voulez avec vos données, il suffit de les référencer par les noms de champs, par exemple en utilisant GNU awk -epour faciliter le mélange d'un script dans un fichier avec un script de ligne de commande:

$ 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
Ed Morton
la source
2
C'est génial, merci beaucoup :)
user356831
Pour ce genre de travail, perlpeut être plus facile à utiliser.
OrangeDog
1
@OrangeDog pourquoi pensez-vous cela? J'aimerais en fait voir l'équivalent en perl si cela ne vous dérange pas de poster une telle réponse. Perl ne sera certainement pas plus facile à utiliser si je ne l'ai pas sur ma boîte et que je ne peux pas l'installer, ce que j'ai souvent dû faire face au fil des ans. Awk d'autre part est un utilitaire obligatoire et est donc toujours présent sur les installations UNIX, tout comme sed, grep, sort, etc.
Ed Morton
@EdMorton true, même si je n'ai jamais personnellement rencontré de distribution où perl n'était pas inclus par défaut. Complexes awket sedscripts sont généralement plus simples perlcar il s'agit essentiellement d'un surensemble d'entre eux, avec des fonctionnalités supplémentaires pour les tâches courantes.
OrangeDog
@OrangeDog personne ne devrait jamais écrire un script sed plus compliqué que s/old/new/gsed et sed n'est pas génial donc laisse cela de côté. Je ne suis absolument pas d'accord pour dire que les scripts awk complexes sont plus simples en perl. Ils peuvent être plus brefs bien sûr, mais la brièveté n'est pas un attribut souhaitable du logiciel, la concision est, et il est extrêmement rare pour eux d'avoir un réel avantage et ils sont généralement beaucoup plus difficiles à lire, c'est pourquoi les gens publient des choses comme zoitz.com / archives / 13 sur perl et y faire référence comme un langage en écriture seule, contrairement à awk. J'aimerais quand même voir un équivalent perl à cela
Ed Morton
13

Avec grep -o, vous devrez faire correspondre exactement ce que vous voulez extraire. Puisque vous ne voulez pas extraire la proto=chaîne, vous ne devez pas la faire correspondre.

Une expression régulière étendue qui correspondrait tcpou serait udpsuivie d'une barre oblique et d'une chaîne alphanumérique non vide est

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

Appliquer cela sur vos données:

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

Pour vous assurer que nous ne le faisons que sur les lignes commençant par la chaîne proto=:

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

Avec sed, tout supprimer avant le premier =et après le premier caractère vierge:

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

Pour vous assurer que nous ne le faisons que sur les lignes commençant par la chaîne proto=, vous pouvez insérer la même étape de prétraitement grepque ci-dessus, ou vous pouvez utiliser

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

Ici, nous supprimons la sortie par défaut avec l' -noption, puis nous déclenchons les substitutions et une impression explicite de la ligne uniquement si la ligne correspond ^proto=.


Avec awk, en utilisant le séparateur de champs par défaut, puis en divisant le premier champ =et en imprimant le deuxième bit:

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

Pour vous assurer que nous ne le faisons que sur les lignes commençant par la chaîne proto=, vous pouvez insérer la même étape de prétraitement grepque ci-dessus, ou vous pouvez utiliser

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

Si vous êtes sur GNU grep (pour l' -Poption), vous pouvez utiliser:

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

Ici, nous faisons correspondre la proto=chaîne, pour nous assurer que nous extrayons la colonne correcte, mais nous la supprimons de la sortie avec l' \Kindicateur.

Ce qui précède suppose que les colonnes sont séparées par des espaces. Si les tabulations sont également un séparateur valide, vous utiliseriez \Spour faire correspondre les caractères non blancs, donc la commande serait:

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

Si vous souhaitez également vous protéger contre les champs de correspondance où se proto=trouve une sous-chaîne, comme un thisisnotaproto=tcp/https, vous pouvez ajouter une limite de mot avec \bceci:

grep -oP '\bproto=\K\S*' file
user000001
la source
1
Vous pouvez améliorer cela en écrivant juste grep -oP 'proto=\K\S+'. Le proto=tcp/httppeut être suivi d'un onglet au lieu d'espaces et, \Scontrairement à [^ ], correspondra à tout caractère non espace.
mosvy
@mosvy: C'est une bonne suggestion, merci.
user000001
1
Quoi qu'il en soit, -oc'est aussi un GNUisme. -Pn'est pris en charge par GNU que greps'il est construit avec le support PCRE (facultatif au moment de la construction).
Stéphane Chazelas
6

En utilisant awk:

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

$1 ~ "proto"assurera que nous n'agissons que sur les lignes avec protodans la première colonne

sub(/proto=/, "") va supprimer proto= de l'entrée

print $1 imprime la colonne restante


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

Code golf sur les grepsolutions

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

ou même

grep -Po "..p/\S+" file
bu5hman
la source
3

Utilisation de la cutcommande:

cut -b 7-15 foo.txt
Capeya
la source
3
Cela comprendra les espaces de fin sur les lignes httpet dns.
G-Man dit `` Réintègre Monica '' le
2

Juste une autre grepsolution:

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

Et un modèle similaire avec sedimpression uniquement du groupe capturé correspondant:

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

Une autre awkapproche:

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

Cela définira le séparateur de champ awk sur l'un =ou sur un espace. Ensuite, si la ligne correspond à a =, alors soit udou tcsuivi de a p, imprimez le 2ème champ.

Une autre sedapproche (non portable sur toutes les versions de sed, mais fonctionne avec GNU sed):

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

Le -nmoyen "ne pas imprimer" et le -Epermet des expressions régulières étendues qui nous donnent \Spour "non-blanc", +pour "un ou plusieurs" et les parenthèses pour la capture. Enfin, /pà la fin fera sed imprimer une ligne uniquement si l'opération a réussi donc s'il y avait une correspondance pour l'opérateur de substitution.

Et, un perl:

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

Le -nmoyen "lit le fichier d'entrée ligne par ligne et applique le script donné par -eà chaque ligne". Le -lajoute une nouvelle ligne à chaque printappel (et supprime les nouvelles lignes sortantes de l'entrée). Le script lui-même imprimera la plus longue séquence de caractères non blancs trouvée après a proto=.

terdon
la source
1
-Edevient de plus en plus portable, mais \Sne l'est pas. [^[:space:]]est un équivalent plus portable.
Stéphane Chazelas
1

Voici une autre solution assez simple:

grep -o "[tc,ud]*p\\/.*  "   INPUTFile.txt  |   awk '{print $1}'
mkzia
la source
Votre grepne correspond à rien. [tc,ud]\*\\/.*recherche une occurrence de t, ou c, ou ,ou uou d, suivie d'un *caractère littéral , puis de a pet d'une barre oblique inverse. Vous vouliez probablement dire grep -Eo '(tc|ud)p/.* ' file | awk '{print $1}'. Mais alors, si vous utilisez awk, vous pouvez aussi bien faire la chose en awk: awk -F'[= ]' '/(tc|ud)p/{print $2}' file.
terdon
Quelqu'un a modifié mon original, il y avait une barre oblique inverse supplémentaire avant l'étoile, que je viens de supprimer, monsieur.
mkzia
Merci pour l'édition, mais je crains que cela ne fonctionne que par hasard. Comme je l' ai expliqué, [tc,ud]psignifie « l' un des t, c, ,, uou dsuivi d'un p. Il correspond ici seulement parce que tcpa cpet udpa dp. Mais il serait également correspondre ,pou tpetc., maintenant que vous avez également le *, il correspondra pppaussi bien (la des *moyens « 0 ou plus » il correspondra même quand il ne correspond pas) vous ne voulez pas une classe de caractères (. [ ]), ce que vous voulez est un groupe: (tc|ud)(utiliser avec le -Edrapeau grep.) en outre, le .*rend correspondre à toute la ligne
terdon
1
@Jesse_b: Bien que mkzia ne soit pas techniquement un «nouveau contributeur», ils sont un utilisateur inexpérimenté, comme en témoigne le fait qu'ils n'ont pas utilisé le formatage de code pour leur commande. Et pourtant, ils étaient assez intelligents pour taper \*pour que le premier *de leur commande apparaisse sous la forme d'un * et non en italique. Lorsque vous mettez la commande au format de code, vous avez fait apparaître \avant le *(provoquant ainsi l'échec de la commande). Lorsque vous modifiez les publications d'autres personnes, faites attention à ne pas modifier l'apparence de la publication comme ceci.
G-Man dit `` Réintègre Monica '' le
@terdon: (1) Non, en fait, cela ne correspondra pas ppp. Bien sûr , vous avez raison qu'il corresponde ,pou  tp- ou uucp, ttp, cutp, ductpou d,up.
G-Man dit `` Réintègre Monica '' le
0
awk '{print $1}' filename|awk -F "=" '{print $NF}'
Praveen Kumar BS
la source
0
cat file| cut -f1 -d' '| cut -f2 -d'='
tcp/http
tcp/https
udp/dns

options de coupe:

  • -f - champ
  • -d - délimètre
Dr. Alexander
la source