Ř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?
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
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
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
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ý.
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 .
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.