O grep pode gerar apenas agrupamentos especificados que correspondem?

233

Digamos que eu tenha um arquivo:

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

Eu só quero saber quais palavras aparecem depois de "foobar", então eu posso usar esse regex:

"foobar \(\w\+\)"

Os parênteses indicam que eu tenho um interesse especial na palavra logo após o foobar. Mas quando eu faço um grep "foobar \(\w\+\)" test.txt , eu obtenho as linhas inteiras que correspondem à regex inteira, em vez de apenas "a palavra depois de foobar":

foobar bash 1
foobar happy

Eu preferiria que a saída desse comando fosse assim:

bash
happy

Existe uma maneira de dizer ao grep para mostrar apenas os itens que correspondem ao agrupamento (ou um agrupamento específico) em uma expressão regular?

    
por Cory Klein 20.05.2011 / 01:04

7 respostas

272

O GNU grep tem a opção -P para as expressões regulares no estilo perl e a opção -o para imprimir apenas o que corresponde ao padrão. Eles podem ser combinados usando asserções de look-around (descritas em Extended Patterns no perlre manpage ) para remover parte do padrão grep do que é determinado que corresponde aos propósitos de -o .

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

O \K é a forma abreviada (e mais eficiente) de (?<=pattern) que você usa como uma asserção de look-behind de largura zero antes do texto que deseja gerar. (?=pattern) pode ser usado como uma asserção de antecipação de largura zero após o texto que você deseja gerar.

Por exemplo, se você quisesse combinar a palavra entre foo e bar , você poderia usar:

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

ou (por simetria)

$ grep -oP '(?<=foo )\w+(?= bar)' test.txt
    
por 20.05.2011 / 03:33
29

O grep padrão não pode fazer isso, mas versões recentes do GNU grep podem . Você pode se transformar em sed, awk ou perl. Aqui estão alguns exemplos que fazem o que você quer em sua entrada de amostra; eles se comportam de maneira ligeiramente diferente nos casos de canto.

Substitua foobar word other stuff por word , imprima somente se uma substituição for feita.

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

Se a primeira palavra for foobar , imprima a segunda palavra.

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

Tira foobar se for a primeira palavra e pula a linha caso contrário; depois tire tudo depois do primeiro espaço em branco e imprima.

perl -lne 's/^foobar\s+// or next; s/\s.*//; print'
    
por 20.05.2011 / 01:17
17
    sed -n "s/^.*foobar\s*\(\S*\).*$//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
     substitute everything with the 1st capture group
p      print it
    
por 22.04.2016 / 18:08
15

Bem, se você sabe que foobar é sempre a primeira palavra ou a linha, então você pode usar o corte. Assim:

grep "foobar" test.file | cut -d" " -f2
    
por 20.05.2011 / 03:07
7

Se o PCRE não for suportado, você poderá obter o mesmo resultado com duas invocações do grep. Por exemplo, para pegar a palavra depois de foobar , faça isso:

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

Isso pode ser expandido para uma palavra arbitrária depois de foobar como este (com EREs para legibilidade):

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

Saída:

1

Observe que o índice i é baseado em zero.

    
por 08.10.2013 / 14:38
3

pcregrep tem uma opção de -o mais inteligente que permite escolher quais grupos de captura você deseja gerar. Então, usando seu arquivo de exemplo,

$ pcregrep -o1 "foobar (\w+)" test.txt
bash
happy
    
por 14.04.2018 / 09:29
2

Usar grep não é compatível com plataformas diferentes, pois -P / --perl-regexp está disponível somente em GNU grep , não BSD grep .

Aqui está a solução usando ripgrep :

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

Como por man rg :

-r/--replace REPLACEMENT_TEXT Replace every match with the text given.

Capture group indices (e.g., $5) and names (e.g., $foo) are supported in the replacement string.

Relacionado: GH-462 .

    
por 16.04.2018 / 17:35