web-dev-qa-db-fra.com

La sortie grep ne peut-elle produire que les groupes spécifiés qui correspondent?

Disons que j'ai un fichier:

# file: 'test.txt'
foobar bash 1
bash
foobar happy
foobar

Je veux seulement savoir quels mots apparaissent après "foobar", donc je peux utiliser cette expression régulière:

"foobar \(\w\+\)"

Les parenthèses indiquent que j'ai un intérêt particulier pour la Parole juste après foobar. Mais quand je fais une grep "foobar \(\w\+\)" test.txt, j'obtiens les lignes entières qui correspondent à toute l'expression régulière, plutôt que juste "le mot après foobar":

foobar bash 1
foobar happy

Je préférerais de beaucoup que la sortie de cette commande ressemble à ceci:

bash
happy

Existe-t-il un moyen de dire à grep de ne sortir que les éléments qui correspondent au regroupement (ou à un regroupement spécifique) dans une expression régulière?

338
Cory Klein

GNU grep a le -P option pour les expressions rationnelles de style Perl et -o option pour imprimer uniquement ce qui correspond au motif. Ceux-ci peuvent être combinés à l'aide d'assertions de recherche (décrites sous Modèles étendus dans la page de manuel perlre ) pour supprimer une partie du modèle grep de ce qui est déterminé comme ayant été mis en correspondance aux fins de -o.

$ grep -oP 'foobar \K\w+' test.txt
bash
happy
$

Le \K est la forme abrégée (et plus efficace) de (?<=pattern) que vous utilisez comme assertion de recherche de largeur nulle avant le texte que vous souhaitez afficher. (?=pattern) peut être utilisé comme assertion d'anticipation de largeur nulle après le texte que vous souhaitez afficher.

Par exemple, si vous souhaitez faire correspondre le mot entre foo et bar, vous pouvez utiliser:

$ grep -oP 'foo \K\w+(?= bar)' test.txt

ou (pour la symétrie)

$ grep -oP '(?<=foo )\w+(?= bar)' test.txt
373
camh

Grep standard ne peut pas faire cela, mais les versions récentes de GNU grep can . Vous pouvez vous tourner vers sed, awk ou Perl. Voici quelques exemples qui font ce que vous voulez sur votre entrée d'échantillon; ils se comportent légèrement différemment dans les cas d'angle.

Remplacer foobar Word other stuff par Word, n'imprime qu'en cas de remplacement.

sed -n -e 's/^foobar \([[:alnum:]]\+\).*/\1/p'

Si le premier mot est foobar, imprimez le deuxième mot.

awk '$1 == "foobar" {print $2}'

Supprimez foobar s'il s'agit du premier mot et sautez la ligne sinon; puis enlevez tout après le premier espace et imprimez.

Perl -lne 's/^foobar\s+// or next; s/\s.*//; print'
    sed -n "s/^.*foobar\s*\(\S*\).*$/\1/p"

-n     suppress printing
s      substitute
^.*    anything before foobar
foobar initial search match
\s*    any white space character (space)
\(     start capture group
\S*    capture any non-white space character (Word)
\)     end capture group
.*$    anything after the capture group
\1     substitute everything with the 1st capture group
p      print it
46
jgshawkey

Eh bien, si vous savez que foobar est toujours le premier mot ou la première ligne, vous pouvez utiliser cut. Ainsi:

grep "foobar" test.file | cut -d" " -f2
19
Dave

pcregrep a une solution plus intelligente -o option qui vous permet de choisir les groupes de capture à afficher. Donc, en utilisant votre fichier d'exemple,

$ pcregrep -o1 "foobar (\w+)" test.txt
bash
happy

Si PCRE n'est pas pris en charge, vous pouvez obtenir le même résultat avec deux appels de grep. Par exemple, pour saisir le mot après foobar, procédez comme suit:

<test.txt grep -o 'foobar  *[^ ]*' | grep -o '[^ ]*$'

Cela peut être développé en un mot arbitraire après foobar comme ceci (avec des ERE pour la lisibilité):

i=1
<test.txt egrep -o 'foobar +([^ ]+ +){'$i'}[^ ]+' | grep -o '[^ ]*$'

Production:

1

Notez que l'index i est basé sur zéro.

9
Thor

L'utilisation de grep n'est pas compatible avec plusieurs plates-formes, car -P/--Perl-regexp n'est disponible que sur GNU grep , pas BSD grep .

Voici la solution en utilisant ripgrep :

$ rg -o "foobar (\w+)" -r '$1' <test.txt
bash
happy

Selon man rg:

-r/--replace REPLACEMENT_TEXT Remplacez chaque correspondance par le texte donné.

Capturez les indices de groupe (par exemple, $5) et les noms (par exemple, $foo) sont pris en charge dans la chaîne de remplacement.

Connexes: GH-462 .

7
kenorb

J'ai trouvé la réponse de @jgshawkey très utile. grep n'est pas un si bon outil pour cela, mais sed l'est, bien que nous ayons ici un exemple qui utilise grep pour saisir une ligne pertinente.

La syntaxe regex de sed est idiosyncrasique si vous n'y êtes pas habitué.

Voici un autre exemple: celui-ci analyse la sortie de xinput pour obtenir un entier ID

⎜   ↳ SynPS/2 Synaptics TouchPad                id=19   [slave  pointer  (2)]

et je veux 19

export TouchPadID=$(xinput | grep 'TouchPad' | sed  -n "s/^.*id=\([[:digit:]]\+\).*$/\1/p")

Notez la syntaxe de classe:

[[:digit:]]

et la nécessité d'échapper aux +

Je suppose qu'une seule correspondance de ligne.

2
Tim Richardson