desenv-web-rp.com

O grep pode gerar apenas grupos especificados correspondentes?

Digamos que eu tenho um arquivo:

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

Eu só quero saber quais palavras aparecem depois de "foobar", para que eu possa usar este regex:

"foobar \(\w\+\)"

Os parênteses indicam que eu tenho um interesse especial na Palavra logo após foobar. Mas quando eu faço uma grep "foobar \(\w\+\)" test.txt, recebo as linhas inteiras que correspondem a toda a regex, em vez de apenas "a Palavra após 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 produzir apenas os itens que correspondem ao agrupamento (ou um agrupamento específico) em uma expressão regular?

338
Cory Klein

O GNU grep tem o -P opção para regexes no estilo Perl, e a -o opção para imprimir apenas o que corresponde ao padrão. Eles podem ser combinados usando asserções de pesquisa (descritas em Padrões estendidos na página de manual do perlre ) para remover parte do padrão grep do que é determinado ter correspondido para os fins de -o.

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

O \K é o formato abreviado (e mais eficiente) de (?<=pattern) que você usa como uma declaração de largura zero antes do texto que você deseja gerar. (?=pattern) pode ser usado como uma afirmação antecipada de largura zero após o texto que você deseja gerar.

Por exemplo, se você deseja combinar a Palavra entre foo e bar, você pode usar:

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

ou (por simetria)

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

O grep padrão não pode fazer isso, mas versões recentes do GNU grep can) . Você pode usar o sed, o awk ou o Perl. Aqui estão alguns exemplos que fazem o que você deseja inserir sua amostra; eles se comportam de maneira um pouco diferente nos cantos.

Substituir foobar Word other stuff por Word, imprima apenas se uma substituição for concluída.

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

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

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

Retire foobar se for a primeira palavra e, caso contrário, pule a linha; depois retire tudo após o primeiro espaço em branco e imprima.

Perl -lne 's/^foobar\s+// or next; s/\s.*//; print'
49
    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

Bem, se você souber que foobar é sempre a primeira palavra ou linha, use cut. Igual a:

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

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

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

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 o seguinte:

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

Isso pode ser expandido para uma palavra arbitrária após foobar assim (com EREs para facilitar a leitura):

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

Resultado:

1

Observe que o índice i é baseado em zero.

9
Thor

O uso de grep não é compatível com várias plataformas, pois -P/--Perl-regexp está disponível apenas em GNU grep , não BSD grep .

Aqui está a solução usando ripgrep :

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

Conforme man rg:

-r/--replace REPLACEMENT_TEXT Substitua todas as correspondências pelo texto fornecido.

Capturar índices do grupo (por exemplo, $5) e nomes (por exemplo, $foo) são suportados na sequência de substituição.

Relacionado: GH-462 .

7
kenorb

Achei a resposta de @jgshawkey muito útil. grep não é uma ferramenta tão boa para isso, mas sed é, embora aqui tenhamos um exemplo que usa grep para pegar uma linha relevante.

A sintaxe da regex do sed é idiossincrática, se você não estiver acostumado.

Aqui está outro exemplo: este analisa a saída do xinput para obter um número inteiro de ID

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

e eu quero 19

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

Observe a sintaxe da classe:

[[:digit:]]

e a necessidade de escapar dos seguintes +

Presumo que apenas uma linha corresponda.

2
Tim Richardson