Como faço para grep para vários padrões em várias linhas?

19

Para ser mais preciso

Some text
begin
Some text goes here.
end
Some more text

e eu quero extrair um bloco inteiro que começa de "begin" até "end".

com awk podemos fazer o mesmo awk '/begin/,/end/' text.

Como fazer com grep?

Iker
fonte
2
Mesma pergunta no Unix e Linux . Não faça isso .
Gilles 'SO- stop be evil'

Respostas:

14

Atualizado 18 de novembro de 2016 (como o comportamento do grep foi alterado: o parâmetro grep com -P agora não suporta ^e $ancora [no Ubuntu 16.04 com kernel v: 4.4.0-21-generic]) ( correção incorreta (não) )

$ grep -Pzo "begin(.|\n)*\nend" file
begin
Some text goes here.  
end

nota: para outros comandos, substitua as âncoras '^' e '$' pela âncora de nova linha '\n' ______________________________

Com o comando grep:

grep -Pzo "^begin\$(.|\n)*^end$" file

Se você quiser não incluir os padrões "begin" e "end" no resultado, use grep com o suporte Lookbehind e Lookahead.

grep -Pzo "(?<=^begin$\n)(.|\n)*(?=\n^end$)" file

Além disso, você pode usar a \Knotificação notificar, em vez da asserção Lookbehind.

grep -Pzo "^begin$\n\K(.|\n)*(?=\n^end$)" file

\KA opção ignora tudo antes da correspondência de padrões e ignora o próprio padrão.
\nusado para evitar a impressão de linhas vazias na saída.

Ou como @AvinashRaj sugere que há grep fácil e simples como a seguir:

grep -Pzo "(?s)^begin$.*?^end$" file

grep -Pzo "^begin\$[\s\S]*?^end$" file

(?s)diz ao grep para permitir que o ponto corresponda a caracteres de nova linha.
[\s\S]corresponde a qualquer caractere que seja espaço em branco ou não em branco.

E a saída deles sem incluir "begin" e "end" é a seguinte:

grep -Pzo "^begin$\n\K[\s\S]*?(?=\n^end$)" file # or grep -Pzo "(?<=^begin$\n)[\s\S]*?(?=\n^end$)"

grep -Pzo "(?s)(?<=^begin$\n).*?(?=\n^end$)" file

veja aqui o teste completo de todos os comandos ( fora de datado, pois o comportamento grep com o parâmetro -P é alterado )

Nota:

^aponte o início de uma linha e $aponte o final de uma linha. estes foram adicionados ao redor de "begin" e "end" para combiná-los se estiverem sozinhos em uma linha.
Em dois comandos, escapei $porque ele também usava para "Substituição de Comando" ( $(command)) que permite que a saída de um comando substitua o nome do comando.

Do man grep:

-o, --only-matching
      Print only the matched (non-empty) parts of a matching line,
      with each such part on a separate output line.

-P, --perl-regexp
      Interpret PATTERN as a Perl compatible regular expression (PCRE)

-z, --null-data
      Treat the input as a set of lines, each terminated by a zero byte (the ASCII 
      NUL character) instead of a newline. Like the -Z or --null option, this option 
      can be used with commands like sort -z to process arbitrary file names.
αғsнιη
fonte
mude seu grep grep -Pzo "(?<=begin\n)(.|\n)*(?=\nend)" filepara não imprimir o \ncaractere existente na linha de início.
precisa
Use dotall modificador para fazer ponto para corresponder até mesmo caracteres de nova linha tambémgrep -Pzo "(?s)begin.*?end" file
Avinash Raj
Ou simplesmente,grep -Pzo "begin[\s\S]*?end" file
Avinash Raj
11
A siólution não funciona. Ela produz um erro: grep: ein nicht geschütztes ^ oder $ wird mit -Pz nicht unterstütztA tradução do erro é algo como:grep: a not protected ^ or $ is not supported with -Pz
Musbach
11
Sim, eu sei, isso está na sua resposta. Tenho certeza de que funcionou quando você postou isso, mas tente novamente hoje. O comportamento de grepparece ter mudado.
terdon
2

Caso você grepnão suporte a sintaxe perl ( -P), você pode tentar unir as linhas, corresponder ao padrão e expandir as linhas novamente, como abaixo:

$ tr '\n' , < foo.txt | grep -o "begin.*end" | tr , '\n'
begin
Some text goes here.
end
kenorb
fonte