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
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 afoo
e faça o que vem dentro dos rabiscos para as linhas correspondentes. Substituafoo
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 abar
. 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ótulostart
que criamos anteriormente, para continuar acrescentando a próxima linha, desde que o espaço padrão não contenhabar
. -
/your_regex/p
imprime o espaço padrão se corresponder ayour_regex
. Você deve substituiryour_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 ondedef
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 quesecond_line_word
esteja na terceira linha, então você não está apenas perdendo a primeira linha (devido a seu segundogrep
), 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ávelall
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
-
/foo/{
é acionado quando a linha contém “foo” -
n
substitua o espaço padrão pela próxima linha, ou seja, a palavra “aqui” -
b gotoloop
ramifica para o rótulo “gotoloop” -
:gotoloop
define o rótulo “gotoloop” -
/bar/!{
se o padrão não contiver “bar” -
h
substitua o espaço de espera por padrão, então “aqui” é salvo no espaço de espera -
b loop
ramificação para o rótulo “loop” -
:loop
define o rótulo “loop” -
N
anexa o padrão ao espaço de espera.
Agora, o espaço de espera contém:
“aqui”
“é o” -
:gotoloop
Estamos agora na etapa 4 e loop até que uma linha contenha “bar” -
/bar/
loop for concluído, “bar” foi encontrado, ele ” é o espaço padrão - 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
-
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.
grep
. Eles estão intimamente relacionados, mas não dups, IMO."grep"
sugerindo o verbo ” para grep ” e as principais respostas, incluindo aceito, não ‘ use grep.