it-swarm-eu.dev

Může grep výstup pouze specifikovaných seskupení, která odpovídají?

Řekněme, že mám soubor:

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

Chci jen vědět, jaká slova se objevují po „foobar“, takže mohu použít tento regex:

"foobar \(\w\+\)"

Závorka ukazuje, že mám zvláštní zájem o slovo hned po foobaru. Ale když udělám grep "foobar \(\w\+\)" test.txt, dostanu celé řádky, které odpovídají celému regexu, nikoli jen „Slovo po foobar“:

foobar bash 1
foobar happy

Raději bych, aby výstup tohoto příkazu vypadal takto:

bash
happy

Existuje způsob, jak říct grepu, aby vydával pouze položky, které odpovídají seskupení (nebo konkrétnímu seskupení) v regulárním výrazu?

338
Cory Klein

GNU grep má -P možnost pro reglety ve stylu Perl a -o možnost tisknout pouze to, co odpovídá vzoru. Lze je kombinovat pomocí tvrzení rozhledů (popsaných v Extended Patterns in perlre manpage ), aby se odstranila část grepového vzoru z toho, co je určeno pro shodu pro účely -o.

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

The \K je krátká forma (a efektivnější forma) (?<=pattern) které použijete jako tvrzení s nulovou šířkou pohledu před textem, který chcete vydat. (?=pattern) může být použito jako tvrzení vpřed s nulovou šířkou po textu, který chcete vydat.

Pokud byste například chtěli porovnat slovo mezi foo a bar, můžete použít:

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

nebo (pro symetrii)

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

Standardní grep to nemůže udělat, ale nedávné verze GNU grep can . Můžete se obrátit na sed, awk nebo Perl. Zde je několik příkladů, které dělají co chcete na svém ukázkovém vstupu, chovají se trochu jinak v rohových případech.

Nahradit foobar Word other stuff by Word, tiskněte, pouze pokud je provedena výměna.

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

Pokud je první slovo foobar, vytiskněte druhé slovo.

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

Pokud je to první slovo, odřízněte foobar a jinak řádek přeskočte; poté vše odstraňte po prvním mezeru a tiskněte.

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

Pokud víte, že foobar je vždy první slovo nebo řádek, můžete použít řez. Jako tak:

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

pcregrep má chytřejší -o možnost, která vám umožní vybrat, které skupiny zachycení chcete výstup. Takže pomocí vzorového souboru

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

Pokud PCRE není podporováno, můžete dosáhnout stejného výsledku pomocí dvou vyvolání grepu. Například chytit Word po foobar udělat:

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

To lze rozšířit na libovolné slovo po foobar takto (s ERE pro čitelnost):

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

Výstup:

1

Všimněte si, že index i je nulový.

9
Thor

Použití grep není kompatibilní napříč platformami, protože -P/--Perl-regexp je k dispozici pouze na GNU grep , nikoli BSD grep .

Zde je řešení pomocí ripgrep :

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

Podle man rg:

-r/--replace REPLACEMENT_TEXT Nahraďte každou shodu daným textem.

Indexy skupin zachycení (např. $5) a jména (např. $foo) jsou podporovány v náhradním řetězci.

Související: GH-462 .

7
kenorb

Odpověď @jgshawkey jsem považovala za velmi užitečnou. grep není na to dobrý nástroj, ale sed je, i když zde máme příklad, který používá grep k uchopení příslušné řádky.

Syntaxe regexu sed je idiosynkratická, pokud na ni nejste zvyklí.

Zde je další příklad: tento analyzuje výstup xinputu, aby získal celé číslo ID

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

a já chci 19

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

Poznámka: syntaxe třídy:

[[:digit:]]

a potřeba uniknout následující +

Předpokládám, že se shoduje pouze jeden řádek.

2
Tim Richardson