grep pula n linhas de arquivo e só pesquisa depois

9

Eu tenho um arquivo de log enorme e quero saudar a primeira ocorrência de um padrão e, em seguida, encontrar outro padrão logo após essa ocorrência.

Por exemplo:

123
XXY
214
ABC
182
558
ABC
856
ABC

No meu exemplo, eu gostaria de encontrar 182e depois encontrar a próxima ocorrência deABC

A primeira ocorrência é simples:

grep -n -m1 "182" /var/log/file

Isso gera:

5:182

Como encontro a próxima ocorrência de ABC?

Minha idéia era dizer greppara pular as primeiras nlinhas (no exemplo acima n=5), com base no número da linha 182. Mas como faço isso?

koljanep
fonte
1
É um requisito grepusado? Eu não acho que isso possa ser feito, grepmas seria fácil com awkou sed(sozinho ou em combinação com grep).
Hauke ​​Laging
@HaukeLaging grepnão é necessário. Ainda não estou tão familiarizado com sedou awk. Se você tem uma boa solução, deixe-me ouvi-la! :) @don_crissti apenas a primeira linha deve ser impressa. Eu não me importo com as outras ocorrências.
koljanep

Respostas:

10

Com sedvocê pode usar uma qentrada range e uit em uma única conclusão:

sed '/^182$/p;//,/^ABC$/!d;/^ABC$/!d;q'

Da mesma forma com o GNU, grepvocê pode dividir a entrada entre dois greps:

{ grep -nxF -m1 182; grep -nxF -m1 ABC; } <<\IN
123
XXY
214
ABC
182
558
ABC
856
ABC
IN

... que imprime ...

5:182
2:ABC

... para significar que o primeiro grepencontrou um -Fliteral de cadeia ixed, -xlinha inteira 182, corresponde a 5 linhas desde o início de sua leitura e o segundo encontrou um ABC de tipo semelhante, correspondente a 2 linhas desde o início de sua leitura - ou 2 linhas após a primeira leitura grep interrompida na linha 5.

De man grep:

-m NUM, --max-count=NUM
          Stop  reading  a  file  after  NUM  matching
          lines.   If the input is standard input from
          a regular file, and NUM matching  lines  are
          output, grep ensures that the standard input
          is  positioned  to  just  after   the   last
          matching  line before exiting, regardless of
          the  presence  of  trailing  context  lines.
          This  enables  a calling process to resume a
          search. 

Eu usei um documento aqui para fins de demonstração reproduzível, mas você provavelmente deveria fazer:

{ grep ...; grep ...; } </path/to/log.file

Também funcionará com outras construções de comando composto do shell, como:

for p in 182 ABC; do grep -nxFm1 "$p"; done </path/to/log.file
mikeserv
fonte
+1 Vi isso na página de manual. Isso é o que eu tentei, apenas com um tubo entre o grep's em vez de um ;... no-go
Xen2050
@ Xen2050 - normalmente, o canal não funciona - um arquivo secundário geralmente é o que você deseja ao compartilhar entradas.
mikeserv
Resposta impressionante, mas não apoio sua declaração sobre pipelines. O documento aqui que os dois grepcompartilham é efetivamente um pipeline para eles. Outra coisa: tentei sem imprimir a linha do marcador, mas sed '//,/^ABC$/!d;/^ABC$/!d;q'emite um erro estranho. O que //faz?
Hauke ​​Laging
1
@HaukeLaging - o documento aqui (na maioria dos shells) não é um pipe - é um arquivo tmp real criado pelo shell que o shell exclui antes de fazer qualquer gravação - enquanto mantém o descritor. Ainda é lseekable. Tubos, geralmente, não são isáveis. Vou dar uma olhada na sedcoisa - acabei de escrever bem rápido.
mikeserv
1
@HaukeLaging - Ah, então a sedcoisa funciona - você deixou de fora a referência. Em sedvocê pode consultar o último /address/novamente com um //endereço vazio . O /^182$/command;//,/next_address/mesmo acontece /^182$/command;/^182$/,/next_address/. Seu erro provavelmente não era uma expressão regular anterior se você estivesse usando um GNU sed. A questão do pipe lseek, a propósito, pode ser manipulada através de /dev/fd/[num]links indiretos nos sistemas linux - mas se você não for muito cuidadoso ao lidar com os buffers (como com dd), isso geralmente é uma batalha perdida.
mikeserv
2

Use grepcom expressões regulares compatíveis com Perl ( pcregrep):

pcregrep -Mo '182(.|\n)*?\KABC'

A opção -Mpermite que o padrão corresponda a mais de uma linha e \Knão inclui o padrão correspondente (até este ponto) na saída. Você pode remover \Kse desejar ter toda a região como resultado.

jimmij
fonte
2
> awk '/^182$/ { startline=1; }; startline == 0 { next; }; /^ABC$/ { print "line " NR ": " $0; exit; }' file
line 7: ABC
Hauke ​​Laging
fonte
1
Isso dá o primeiro ABC em qualquer lugar ; esta pergunta quer o primeiro ABC após o primeiro 182. O mais direto é um sinalizador como awk '/^182$/{z=1;next} z&&/^ABC$/{print NR":"$0;exit}' file- ou você pode escrever pelo menos um getline()loop explícito que geralmente é mais desajeitado ou ser inteligente (?) usando um intervalo quase como o perl de JRFerguson:awk '!x&&/^182$/,/^ABC$/ {x=NR":"$0} END{print x}
dave_thompson_085
@ dave_thompson_085 De fato. Idéia certa, mas terrivelmente codificada (misturou duas idéias durante a escrita). Embaraçosamente, eu até tentei, mas não me surpreendi com a saída.
Hauke ​​Laging
1

Uma variação do Perl que você pode usar é:

perl -nle 'm/182/../ABC/ and print' file

... que imprime linhas no intervalo correspondente.

Se o arquivo contiver mais de um intervalo correspondente, você poderá limitar a saída apenas ao primeiro intervalo, alterando o /delimitador para?

perl -nle 'm?182?..?ABC? and print'
JRFerguson
fonte
1

Aderindo apenas grepe adicionando tail& cut, você pode ...

grep para o número da linha da primeira correspondência de 182:

grep -m 1 -n 182 /var/log/file |cut -f1 -d:

Use isso para grep para todos os caracteres ABCsomente após a primeira linha correspondente acima, usando tails -n +Kpara produzir após a linha K'th. Todos juntos:

tail -n +$(grep -m 1 -n 182 /var/log/file |cut -f1 -d:) /var/log/file | grep ABC

Ou adicione -m 1novamente para encontrar apenas a primeira correspondênciaABC

tail -n +$(grep -m 1 -n 182 /var/log/file|cut -f1 -d:) /var/log/file|grep -m 1 ABC

Referências:
manpáginas
/programming/6958841/use-grep-to-report-back-only-line-numbers

Xen2050
fonte
1

Outra variante é esta:

grep -n -A99999 "182" /var/log/file|grep -n -m1 "ABC"

A bandeira -An cumprimenta n linhas após a partida e 99999 é apenas para garantir que não perdemos nada. Arquivos maiores devem ter mais linhas (verifique com "wc -l").

Fabbe
fonte
0

O operador de intervalo ,pode ser usado aqui:

< yourfile \
sed -e '
   /182/,/ABC/!d
   //!d;=;/ABC/q
' | sed -e 'N;s/\n/:/'

O operador de intervalo ..em conjunto com o operador de correspondência única, m??pode ser usado aqui emPerl

perl -lne 'm?182? .. m?ABC? and print "$.:$_" if /182/ || /ABC/' yourfile

fonte