Comment imprimer un motif regex correspondant à l'aide de awk?

109

En utilisant awk, j'ai besoin de trouver un mot dans un fichier qui correspond à un modèle regex.

Moi seulement veux imprimer le mot correspondant au motif.

Donc, si dans la ligne, j'ai:

xxx yyy zzz

Et motif:

/yyy/

Je veux seulement obtenir:

yyy

EDIT: grâce à kurumi, j'ai réussi à écrire quelque chose comme ceci:

awk '{
        for(i=1; i<=NF; i++) {
                tmp=match($i, /[0-9]..?.?[^A-Za-z0-9]/)
                if(tmp) {
                        print $i
                }
        }
}' $1

et c'est ce dont j'avais besoin :) merci beaucoup!

Marverix
la source
1
@maxtaldykin Pourriez-vous déplacer votre auto-réponse de la question vers une réponse séparée s'il vous plaît?
kenorb
2
Vous n'avez pas besoin de le faire tmp=match($i, /regexp);if(tmp){}, vous devriez simplement pouvoir le faire if(tmp ~ $i){}parce que ~signifie «correspond à l'expression rationnelle».
JustinCB

Réponses:

148

C'est le très basique

awk '/pattern/{ print $0 }' file

demandez awkde rechercher patternusing //, puis imprimez la ligne, qui par défaut est appelée un enregistrement, notée $ 0. Au moins, lisez la documentation .

Si vous souhaitez uniquement imprimer le mot correspondant.

awk '{for(i=1;i<=NF;i++){ if($i=="yyy"){print $i} } }' file
Kurumi
la source
49
Puisque printc'est l'action par défaut: awk '/pattern/' filesuffira.
Johnsyweb le
18
@Johnsyweb, oui je sais ce fait. Pour un débutant comme Marverix, c'est censé être plus visuel.
kurumi le
21
Je ne doute pas de vos connaissances. Cependant, les informations peuvent être utiles à d'autres personnes qui trouvent cette réponse.
Johnsyweb
2
NB: @marverix devra faire un peu plus de travail pour faire fonctionner la forboucle si (a) "yyy" est une expression régulière et non une chaîne droite et (b) si ce "yyy" ne correspond pas à un champ entier dans un enregistrement.
Johnsyweb
8
Ce ne serait pas le cas $i=="yyy"; ce serait $i ~ /yyy/pour une expression régulière.
JustinCB
118

Il semble que vous essayez d'imiter le grep -ocomportement de GNU . Cela fera cela à condition que vous ne vouliez que la première correspondance sur chaque ligne:

awk 'match($0, /regex/) {
    print substr($0, RSTART, RLENGTH)
}
' file

Voici un exemple, en utilisant GNU awk implémentation ():

awk 'match($0, /a.t/) {
    print substr($0, RSTART, RLENGTH)
}
' /usr/share/dict/words | head
act
act
act
act
aft
ant
apt
art
art
art

En savoir plus match , substr, RSTARTet RLENGTHdans le awkmanuel.

Après cela, vous souhaiterez peut-être étendre cela pour traiter plusieurs correspondances sur la même ligne.

Johnsyweb
la source
NB: Pour répondre à cette dernière partie, toutes les constructions nécessaires sont dans la réponse de Kurumi et la mienne.
Johnsyweb le
Très bonne réponse. Je voudrais juste une explication ici en place parce que je suis paresseux. Mais c'est pourquoi j'utilise AWK!
lukas.pukenis
Que faire si je veux faire quelque chose avec le résultat du match sauf de l'imprimer? Par exemple, je veux ajouter toutes les correspondances dans le tableau.
Evya2005
@ evya2005: Vous pouvez simplement remplacer l'impression Ron d'appel par l'affectation dont vous avez besoin.
Johnsyweb
Ça ne fonctionne pas pour moi. seulement les travaux d'impression. pouvez-vous me montrer un exemple?
Evya2005
36

gawk peut obtenir la partie correspondante de chaque ligne en utilisant ceci comme action:

{ if (match($0,/your regexp/,m)) print m[0] }

match (string, regexp [, array]) Si tableau est présent, il est effacé, puis l'élément zéro du tableau est défini sur la partie entière de la chaîne mise en correspondance par regexp. Si l'expression rationnelle contient des parenthèses, les éléments indexés en entier du tableau sont définis pour contenir la partie de la chaîne correspondant à la sous-expression entre parenthèses correspondante. http://www.gnu.org/software/gawk/manual/gawk.html#String-Functions

royas
la source
13

Si vous n'êtes intéressé que par la dernière ligne d'entrée et que vous prévoyez de ne trouver qu'une seule correspondance (par exemple une partie de la ligne récapitulative d'une commande shell), vous pouvez également essayer ce code très compact, adopté depuis Comment imprimer les correspondances de regexp en utilisant `awk`? :

$ echo "xxx yyy zzz" | awk '{match($0,"yyy",a)}END{print a[0]}'
yyy

Ou la version plus complexe avec un résultat partiel:

$ echo "xxx=a yyy=b zzz=c" | awk '{match($0,"yyy=([^ ]+)",a)}END{print a[1]}'
b

Attention: la awk match()fonction à trois arguments n'existe que dans gawk, pas dansmawk

Voici une autre solution intéressante utilisant une expression régulière lookbehind au greplieu de awk. Cette solution a des exigences moindres pour votre installation:

$ echo "xxx=a yyy=b zzz=c" | grep -Po '(?<=yyy=)[^ ]+'
b
Daniel Alder
la source
Pourquoi avez-vous ajouté "tail -n1"? Cela devrait bien fonctionner sans lui, non?
Arthur Accioly
1
@ArthurAccioly Correct. J'ai utilisé le terme pour extraire le temps aller-retour moyen d'un appel ping, c'est de là que cela vient. drôle qu'il ait fallu 4 ans pour le découvrir;)
Daniel Alder
12

Si Perl est une option, vous pouvez essayer ceci:

perl -lne 'print $1 if /(regex)/' file

Pour implémenter la correspondance insensible à la casse, ajoutez le imodificateur

perl -lne 'print $1 if /(regex)/i' file

Pour tout imprimer APRÈS le match:

perl -lne 'if ($found){print} else{if (/regex(.*)/){print $1; $found++}}' textfile

Pour imprimer le match et tout après le match:

perl -lne 'if ($found){print} else{if (/(regex.*)/){print $1; $found++}}' textfile
Chris Koknat
la source
3

L'utilisation de sed peut également être élégante dans cette situation. Exemple (remplacez la ligne par le groupe correspondant "yyy" de la ligne):

$ cat testfile
xxx yyy zzz
yyy xxx zzz
$ cat testfile | sed -r 's#^.*(yyy).*$#\1#g'
yyy
yyy

Page de manuel pertinente: https://www.gnu.org/software/sed/manual/sed.html#Back_002dreferences-and-Subexpressions

Konrad Brodzik
la source
Pour les non-gnu sed, la solution est quelque chose comme ceci:sed -n 's/^.*\(yyy\).*$/\1/gp' < testfile
Grigory Entin
1
@GrigoryEntin - bsd sed fonctionne bien avec la réponse d'origine. Le commutateur regex étendu pris en charge par POSIX est -E, mais dans FreeBSD au moins -r est le même que -E (-r ajouté en 2010). Quoi qu'il en soit, essayez avec -E (gnu sed ajouté -E en 4.3)
Juan
3

Hors sujet, cela peut être fait en utilisant le grep également, il suffit de le poster ici au cas où quelqu'un chercherait une solution grep

echo 'xxx yyy zzze ' | grep -oE 'yyy'
Zeus
la source
Un moyen simple de le saisir même avec regex. Exactement ce dont j'avais besoin. Merci!
Marquee
Cela fonctionne pour moi; Mon cas est comme: echo "web_port = 8080, shutdown_port = 8005" | grep -oE "web_port = [0-9] +" # return 8080
Robb Tsang
0

Si vous savez dans quelle colonne se trouve le texte / motif que vous recherchez (par exemple "yyy"), vous pouvez simplement vérifier cette colonne spécifique pour voir si elle correspond et l'imprimer.

Par exemple, étant donné un fichier avec le contenu suivant, (appelé asdf.txt )

xxx yyy zzz

pour n'imprimer que la deuxième colonne si elle correspond au modèle "yyy", vous pouvez faire quelque chose comme ceci:

awk '$2 ~ /yyy/ {print $2}' asdf.txt

Notez que cela correspondra également à n'importe quelle ligne où la deuxième colonne contient un "yyy", comme ceci:

xxx yyyz zzz
xxx zyyyz
Kimbo
la source