Parece que estou fazendo mau uso de grep / egrep.

Eu estava tentando pesquisar strings em várias linhas e não consegui encontrar uma correspondência, embora eu saiba que o que estou procurando deveria corresponder. Originalmente, pensei que minhas regexes estavam erradas, mas acabei lendo que essas ferramentas operam por linha (também minhas regexes eram tão triviais que não poderiam ser o problema).

Qual ferramenta seria usada para pesquisar padrões em várias linhas?

Comentários

  • possível duplicata de Correspondência de padrão multilinha usando sed, awk ou grep
  • @CiroSantilli – Não acho que este Q e aquele ao qual você vinculou sejam duplicados. O outro Q está perguntando como você ‘ d corresponderia a padrões de várias linhas (ou seja, qual ferramenta devo / posso use para fazer isso) enquanto este está perguntando como fazer isso com grep. Eles estão intimamente relacionados, mas não dups, IMO.
  • @sim, esses casos são difícil de decidir: entendo seu ponto de vista. Acho que este caso específico é melhor como uma duplicata, porque se o usuário disse "grep" sugerindo o verbo ” para grep ” e as principais respostas, incluindo aceito, não ‘ use grep.
  • Não há indicação para mostrar que uma expressão regular multilinha é necessária aqui. Considere mostrar um exemplo real com dados de entrada e dados de saída esperados, bem como seu esforço anterior.

Resposta

Aqui, “sa sed um que lhe dará um comportamento grep semelhante em várias linhas:

sed -n "/foo/{:start /bar/!{N;b start};/your_regex/p}" your_file 

Como funciona

  • -n suprime o comportamento padrão de impressão de cada linha
  • /foo/{} o instrui a corresponder a foo e faça o que vem dentro dos rabiscos para as linhas correspondentes. Substitua foo pela parte inicial do padrão.
  • :start é um rótulo de ramificação para nos ajudar a manter o loop até encontrarmos o fim de nosso regex.
  • /bar/!{} executará o que está nos rabiscos para as linhas que não correspondem a bar. Substitua com a parte final do padrão.
  • N anexa a próxima linha ao buffer ativo (sed chama isso de espaço padrão)
  • b start irá desviar incondicionalmente para o rótulo start que criamos anteriormente, para continuar acrescentando a próxima linha, desde que o espaço padrão não contenha bar.
  • /your_regex/p imprime o espaço padrão se corresponder a your_regex. Você deve substituir your_regex por toda a expressão que deseja corresponder em várias linhas.

Comentários

  • +1 Adicionando isso à ferramenta! Obrigado.
  • Observação: no MacOS, isso dá sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
  • Obtendo o sed: unterminated { erro
  • @Nomaed Shot in the dark aqui, mas seu regex contém algum ” {” caracteres? Nesse caso, você ‘ precisará fazer um escape com barra invertida.
  • @Nomaed Parece que tem a ver com as diferenças entre sed implementações. Tentei seguir as recomendações dessa resposta para tornar o script acima compatível com os padrões, mas ele me disse que ” start ” era um indefinido rótulo. Portanto, ‘ não tenho certeza se isso pode ser feito de maneira compatível com o padrão. Se você conseguir, fique à vontade para editar minha resposta.

Resposta

Eu geralmente uso uma ferramenta chamado pcregrep que pode ser instalado na maior parte do tipo de linux usando yum ou apt.

Por exemplo.

Suponha que você tenha um arquivo chamado testfile com conteúdo

abc blah blah blah def blah blah blah 

Você pode executar o seguinte comando:

$ pcregrep -M "abc.*(\n|.)*def" testfile 

para fazer a correspondência de padrões em várias linhas.

Além disso, você pode fazer o mesmo com sed também.

$ sed -e "/abc/,/def/!d" testfile 

Comentários

  • esta sed sugestão pula a linha onde def seria encontrada

Resposta

Simplesmente um grep normal que suporta Perl-regexp parâmetro P fará esse trabalho.

$ echo "abc blah blah blah def blah blah blah" | grep -oPz "(?s)abc.*?def" abc blah blah blah def 

(?s) chamado modificador DOTALL que faz com que o ponto na sua regex corresponda não apenas aos caracteres, mas também às quebras de linha.

Comentários

  • Quando tento esta solução, a saída não termina em ‘ def ‘ mas vai para o final do arquivo ‘ blah ‘
  • talvez seu grep não é compatível com a -P opção
  • Esta foi a única que funcionou para mim – tentei todas as sed sugestões, mas não ‘ não foi tão longe quanto instalar alternativas grep.
  • $ grep --version: grep (GNU grep) 3.1 no Windows Git Bash tem uma opção -P, --perl-regexp mas (?s) não ‘ t parece funcionar lá. Ainda mostra apenas a primeira linha. O mesmo padrão com a mesma string de teste funciona em regex101.com . Existe uma alternativa no Git Bash? sed? (sed (GNU sed) 4.8 aqui)
  • Você sabe como adicionar contexto à saída? grep -1 não ‘ não funciona aqui.

Resposta

Aqui, “uma abordagem mais simples usando Perl:

perl -e "$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m" file 

ou (já que JosephR pegou o sed rota , eu vou roubar descaradamente sua sugestão )

perl -n000e "print $& while /^foo.*\nbar.*\n/mg" file 

### Explicação

$f=join("",<>);: lê o arquivo inteiro e salva seu conteúdo (novas linhas e tudo) na variável $f. Em seguida, tentamos corresponder foo\nbar.*\n e imprimi-lo se corresponder (a variável especial $& contém a última correspondência encontrada). O ///m é necessário para fazer a correspondência da expressão regular em novas linhas.

O -0 define o separador de registro de entrada. Definir isso como 00 ativa o “modo de parágrafo” onde Perl usará novas linhas consecutivas (\n\n) como o separador de registro. Nos casos em que não há novas linhas consecutivas, todo o arquivo é lido (slurped) de uma vez.

### Aviso: não faça isso para arquivos grandes, ele irá carregar o arquivo inteiro na memória e isso pode ser um problema.

Comentários

  • Eu não ‘ t sabe muito sobre Perl, mas não ‘ ele precisa ser my $f=join("",<>);, estritamente falando?
  • @Sapphire_Brick apenas se você estiver no modo estrito (use strict;). É ‘ um bom hábito a se adquirir, especialmente ao escrever scripts maiores, mas ‘ é um exagero para uma pequena linha como esta um.

Resposta

Suponha que temos o arquivo test.txt contendo:

blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla 

O seguinte código pode ser usado:

sed -n "/foo/,/bar/p" test.txt 

Para a seguinte saída:

foo here is the text to keep between the 2 patterns bar 

Resposta

A alternativa grep sift suporta correspondência multilinha (isenção de responsabilidade: eu sou o autor).

Suponha que testfile contém:

 <book> <title>Lorem Ipsum</title> <description>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</description> </book> 

sift -m '<description>.*?</description>' (mostrar as linhas que contêm a descrição)

Resultado:

 testfile: <description>Lorem ipsum dolor sit amet, consectetur testfile: adipiscing elit, sed do eiusmod tempor incididunt ut testfile: labore et dolore magna aliqua</description> 

sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (extrair e reformate a descrição)

Resultado:

description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" 

Comentários

  • Ferramenta muito boa. Parabéns! Tente incluí-lo em distribuições como o Ubuntu.

Resposta

Resolvi este para mim usando grep e – Uma opção com outro grep.

grep first_line_word -A 1 testfile | grep second_line_word 

A opção -A 1 imprime 1 linha após a linha encontrada. Claro que depende do seu arquivo e combinação de palavras. Mas para mim foi a solução mais rápida e confiável.

Comentários

  • alias grepp = ‘ grep –color = auto -B10 -A20 -i ‘ então cat somefile | grepp blah | grepp foo | barra grepp … sim, aqueles -A e -B são muito úteis …você tem a melhor resposta
  • Isso não é ‘ t superdeterminista e ignora todo o padrão em favor de obter apenas uma única linha diferente (apenas com base em sua proximidade para a primeira linha). É ‘ melhor dizer ao programa para ir até onde for necessário para chegar a algum tipo de padrão que você ‘ re certeza absoluta é o final do texto que você ‘ está tentando corresponder. Por exemplo, se testfile for atualizado de forma que second_line_word esteja na terceira linha, então você não está apenas perdendo a primeira linha (devido a seu segundo grep), mas você ‘ não está perdendo a linha que começou a aparecer entre os dois.
  • Isso seria um MO bom o suficiente para comandos ad hoc onde você realmente deseja apenas uma única linha na saída que você já entendeu. Eu não ‘ não acho que ‘ é o que o OP busca e você provavelmente também poderia copiar / colar nesse ponto devido a sendo ad hoc.

Resposta

Uma maneira de fazer isso é com Perl. por exemplo. aqui está o conteúdo de um arquivo chamado foo:

foo line 1 bar line 2 foo foo foo line 5 foo bar line 6 

Agora, aqui está algum Perl que irá corresponde a qualquer linha que comece com foo seguida por qualquer linha que comece com bar:

cat foo | perl -e "while(<>){$all .= $_} while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) { print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m; }" 

O Perl, dividido:

  • while(<>){$all .= $_} Carrega toda a entrada padrão na variável $all
  • while($all =~ Embora a variável all tenha a expressão regular …
  • /^(foo[^\n]*\nbar[^\n]*\n)/m O regex: foo no início da linha, seguido por qualquer número de caracteres que não sejam de nova linha, seguido por uma nova linha, seguido imediatamente por “bar” e o resto da linha com bar. /m no final da regex significa “correspondência em várias linhas”
  • print $1 Imprima a parte da regex que estava entre parênteses (neste caso, a expressão regular inteira)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Apaga a primeira correspondência para o regex, para que possamos combinar vários casos do regex no arquivo em questão

E a saída:

foo line 1 bar line 2 foo bar line 6 

Comentários

  • Passei aqui para dizer que seu Perl pode ser encurtado para o mais idiomático: perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo

Resposta

Se quisermos obter o texto entre os 2 padrões, excluindo-se a si próprios.

Suponha que temos o arquivo test.txt contendo:

blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla 

O seguinte código pode ser usado:

 sed -n "/foo/{ n b gotoloop :loop N :gotoloop /bar/!{ h b loop } /bar/{ g p } }" test.txt 

Para o seguinte resultado:

here is the text to keep between the 2 patterns 

Como funciona, vamos “s faça passo a passo

  1. /foo/{ é acionado quando a linha contém “foo”
  2. n substitua o espaço padrão pela próxima linha, ou seja, a palavra “aqui”
  3. b gotoloop ramifica para o rótulo “gotoloop”
  4. :gotoloop define o rótulo “gotoloop”
  5. /bar/!{ se o padrão não contiver “bar”
  6. h substitua o espaço de espera por padrão, então “aqui” é salvo no espaço de espera
  7. b loop ramificação para o rótulo “loop”
  8. :loop define o rótulo “loop”
  9. N anexa o padrão ao espaço de espera.
    Agora, o espaço de espera contém:
    “aqui”
    “é o”
  10. :gotoloop Estamos agora na etapa 4 e loop até que uma linha contenha “bar”
  11. /bar/ loop for concluído, “bar” foi encontrado, ele ” é o espaço padrão
  12. o espaço do padrão é substituído pelo espaço de espera que contém todas as linhas entre” foo “e” bar “que foram salvas durante o loop principal
  13. p copiar o espaço do padrão para a saída padrão

Feito!

Comentários

  • Muito bem, +1. Eu geralmente evito usar esses comandos tr ‘ colocando as novas linhas em SOH e executando comandos normais do sed e, em seguida, substituindo as novas linhas.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *