it-swarm.it

Grep può produrre solo raggruppamenti specifici corrispondenti?

Di 'che ho un file:

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

Voglio solo sapere quali parole compaiono dopo "foobar", quindi posso usare questa regex:

"foobar \(\w\+\)"

Le parentesi indicano che ho un interesse speciale per la Parola subito dopo il foobar. Ma quando faccio una grep "foobar \(\w\+\)" test.txt, ottengo le intere righe che corrispondono all'intera regex, piuttosto che solo "la parola dopo il foobar":

foobar bash 1
foobar happy

Preferirei di gran lunga che l'output di quel comando fosse simile al seguente:

bash
happy

C'è un modo per dire a grep di produrre solo gli elementi che corrispondono al raggruppamento (o un raggruppamento specifico) in un'espressione regolare?

338
Cory Klein

GNU grep ha il -P opzione per regex in stile Perl e -o opzione per stampare solo ciò che corrisponde al motivo. Questi possono essere combinati usando asserzioni look-around (descritte in Extended Patterns nella perlre manpage ) per rimuovere parte del pattern grep da ciò che si ritiene corrisponda ai fini di -o.

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

Il \K è la forma abbreviata (e la forma più efficiente) di (?<=pattern) che utilizzi come asserzione look-behind a larghezza zero prima del testo che desideri produrre. (?=pattern) può essere utilizzato come un'asserzione di previsione a larghezza zero dopo il testo che si desidera produrre.

Ad esempio, se si desidera abbinare la parola tra foo e bar, è possibile utilizzare:

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

o (per simmetria)

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

Grep standard non può farlo, ma versioni recenti di GNU grep can . Puoi passare a sed, awk o Perl. Ecco alcuni esempi che fanno cosa vuoi sul tuo input di esempio; si comportano in modo leggermente diverso in casi angolari.

Sostituisci foobar Word other stuff by Word, stampa solo se viene eseguita una sostituzione.

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

Se la prima parola è foobar, stampa la seconda parola.

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

Rimuovi foobar se è la prima parola e salta la riga altrimenti; quindi rimuovere tutto dopo il primo spazio bianco e stampare.

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

Bene, se sai che il foobar è sempre la prima Parola o la riga, allora puoi usare il taglio. Così:

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

pcregrep ha un'opzione -o più intelligente che ti consente di scegliere quali gruppi di acquisizione vuoi produrre. Quindi, usando il tuo file di esempio,

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

Se PCRE non è supportato, puoi ottenere lo stesso risultato con due invocazioni di grep. Ad esempio per afferrare la Parola dopo foobar fai questo:

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

Questo può essere espanso in una parola arbitraria dopo foobar in questo modo (con ERE per la leggibilità):

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

Produzione:

1

Nota che l'indice i è a base zero.

9
Thor

L'uso di grep non è compatibile con più piattaforme, poiché -P/--Perl-regexp è disponibile solo su GNU grep , non BSD grep .

Ecco la soluzione usando ripgrep :

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

Secondo man rg:

-r/--replace REPLACEMENT_TEXT Sostituisci ogni corrispondenza con il testo indicato.

Acquisisci indici di gruppo (ad es. $5) e nomi (ad es. $foo) sono supportati nella stringa di sostituzione.

Correlati: GH-462 .

7
kenorb

Ho trovato molto utile la risposta di @jgshawkey. grep non è un ottimo strumento per questo, ma sed lo è, anche se qui abbiamo un esempio che usa grep per afferrare una linea rilevante.

La sintassi Regex di sed è idiosincratica se non ci si è abituati.

Ecco un altro esempio: questo analizza l'output di xinput per ottenere un numero intero ID

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

e voglio 19

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

Nota la sintassi della classe:

[[:digit:]]

e la necessità di sfuggire al seguente +

Presumo che corrispondano solo a una riga.

2
Tim Richardson