Como selecionar linhas entre dois padrões?

Eu tenho um arquivo como o seguinte e gostaria de imprimir as linhas entre dois padrões determinados PAT1 e PAT2 .

 1 2 PAT1 3 - first block 4 PAT2 5 6 PAT1 7 - second block PAT2 8 9 PAT1 10 - third block 

Eu li Como selecionar linhas entre dois padrões de marcadores que podem ocorrer várias vezes com o awk / sed, mas estou curioso para ver todas as combinações possíveis disso, seja imprimindo o padrão ou não.

Como posso selecionar as linhas entre dois padrões?

Imprimir linhas entre o PAT1 e o PAT2

 $ awk '/PAT1/,/PAT2/' file PAT1 3 - first block 4 PAT2 PAT1 7 - second block PAT2 PAT1 10 - third block 

Ou, usando variables:

 awk '/PAT1/{flag=1} flag; /PAT2/{flag=0}' file 

Como é que isso funciona?

  • /PAT1/ corresponde linhas com este texto, bem como /PAT2/ faz.
  • /PAT1/{flag=1} define o flag quando o texto PAT1 é encontrado em uma linha.
  • /PAT2/{flag=0} desmarca o flag quando o texto PAT2 é encontrado em uma linha.
  • flag é um padrão com a ação padrão, que é print $0 : se o flag for igual a 1, a linha será impressa. Desta forma, irá imprimir todas as linhas que ocorrem desde o tempo PAT1 e até o próximo PAT2 é visto. Isso também imprimirá as linhas da última correspondência de PAT1 até o final do arquivo.

Imprimir linhas entre PAT1 e PAT2 – não incluindo PAT1 e PAT2

 $ awk '/PAT1/{flag=1; next} /PAT2/{flag=0} flag' file 3 - first block 4 7 - second block 10 - third block 

Isso usa next para pular a linha que contém PAT1 para evitar que isso seja impresso.

Esta chamada para o next pode ser descartada pelo reenquadramento dos blocos: awk '/PAT2/{flag=0} flag; /PAT1/{flag=1}' file awk '/PAT2/{flag=0} flag; /PAT1/{flag=1}' file .

Imprimir linhas entre PAT1 e PAT2 – incluindo PAT1

 $ awk '/PAT1/{flag=1} /PAT2/{flag=0} flag' file PAT1 3 - first block 4 PAT1 7 - second block PAT1 10 - third block 

Colocando o flag no final, ele aciona a ação que foi definida no PAT1 ou no PAT2: para imprimir no PAT1, não para imprimir no PAT2.

Imprimir linhas entre PAT1 e PAT2 – incluindo PAT2

 $ awk 'flag; /PAT1/{flag=1} /PAT2/{flag=0}' file 3 - first block 4 PAT2 7 - second block PAT2 10 - third block 

Colocando o flag no início, ele aciona a ação que foi definida anteriormente e, portanto, imprime o padrão de fechamento, mas não o padrão inicial.

Imprimir linhas entre PAT1 e PAT2 – excluindo as linhas do último PAT1 até o final do arquivo, se nenhum outro PAT2 ocorrer

Isso é baseado em uma solução de Ed Morton .

 awk 'flag{ if (/PAT2/) {printf "%s", buf; flag=0; buf=""} else buf = buf $0 ORS } /PAT1/ {flag=1}' file 

Como um one-liner:

 $ awk 'flag{ if (/PAT2/){printf "%s", buf; flag=0; buf=""} else buf = buf $0 ORS}; /PAT1/{flag=1}' file 3 - first block 4 7 - second block # note the lack of third block, since no other PAT2 happens after it 

Isso mantém todas as linhas selecionadas em um buffer que é preenchido a partir do momento em que o PAT1 é encontrado. Então, ele continua sendo preenchido com as seguintes linhas até que o PAT2 seja encontrado. Nesse ponto, ele imprime o conteúdo armazenado e esvazia o buffer.

E quanto à solução sed clássica?

Imprimir linhas entre o PAT1 e o PAT2

 sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' file 

ou até mesmo (Obrigado Sundeep ):

 sed -n '/PAT1/,/PAT2/{//!p}' 

O acima exclui as bordas do intervalo.

Imprimir linhas entre PAT1 e PAT2 – incluindo PAT1 e PAT2

O seguinte includeia as bordas do intervalo, o que é ainda mais simples:

 sed -n '/PAT1/,/PAT2/p' file 

Imprimir linhas entre PAT1 e PAT2 – incluindo PAT1

O seguinte inclui apenas o início do intervalo:

 sed -n '/PAT1/,/PAT2/{/PAT2/!p}' file 

Imprimir linhas entre PAT1 e PAT2 – incluindo PAT2

O seguinte inclui apenas o final do intervalo:

 sed -n '/PAT1/,/PAT2/{/PAT1/!p}' file 

Usando grep com PCRE (quando disponível) para imprimir marcadores e linhas entre marcadores :

 $ grep -Pzo "(?s)(PAT1(.*?)(PAT2|\Z))" file PAT1 3 - first block 4 PAT2 PAT1 7 - second block PAT2 PAT1 10 - third block 
  • -P perl-regexp, PCRE. Não em todas as variantes do grep
  • -z Trate a input como um conjunto de linhas, cada terminado por um byte zero em vez de uma nova linha
  • -o imprimir apenas correspondência
  • (?s) DotAll, ou seja. ponto encontra novas linhas bem
  • (.*?) nongreedy encontrar
  • \Z Match apenas no final da string, ou antes da nova linha no final

Imprimir linhas entre marcadores, excluindo o marcador final :

 $ grep -Pzo "(?s)(PAT1(.*?)(?=(\nPAT2|\Z)))" file PAT1 3 - first block 4 PAT1 7 - second block PAT1 10 - third block 
  • (.*?)(?=(\nPAT2|\Z)) nongreedy encontra com lookahead para \nPAT2 e \Z

Linhas de impressão entre marcadores, excluindo marcadores :

 $ grep -Pzo "(?s)((?<=PAT1\n)(.*?)(?=(\nPAT2|\Z)))" file 3 - first block 4 7 - second block 10 - third block 
  • (?<=PAT1\n) lookbehind positivo para PAT1\n

Imprimir linhas entre marcadores, excluindo o marcador de início :

 $ grep -Pzo "(?s)((?<=PAT1\n)(.*?)(PAT2|\Z))" file 3 - first block 4 PAT2 7 - second block PAT2 10 - third block 

Aqui está outra abordagem

Inclua os dois padrões (padrão)

 $ awk '/PAT1/,/PAT2/' file PAT1 3 - first block 4 PAT2 PAT1 7 - second block PAT2 PAT1 10 - third block 

Mascarar ambos os padrões

 $ awk '/PAT1/,/PAT2/{if(/PAT2|PAT1/) next; print}' file 3 - first block 4 7 - second block 10 - third block 

Padrão de início de máscara

 $ awk '/PAT1/,/PAT2/{if(/PAT1/) next; print}' file 3 - first block 4 PAT2 7 - second block PAT2 10 - third block 

Padrão final de máscara

 $ awk '/PAT1/,/PAT2/{if(/PAT2/) next; print}' file PAT1 3 - first block 4 PAT1 7 - second block PAT1 10 - third block 

Você pode fazer o que quiser com sed , suprimindo a impressão normal do espaço padrão com -n . Por exemplo, para include os padrões no resultado que você pode fazer:

 $ sed -n '/PAT1/,/PAT2/p' filename PAT1 3 - first block 4 PAT2 PAT1 7 - second block PAT2 PAT1 10 - third block 

Para excluir os padrões e apenas imprimir o que está entre eles:

 $ sed -n '/PAT1/,/PAT2/{/PAT1/{n};/PAT2/{d};p}' filename 3 - first block 4 7 - second block 10 - third block 

Que decompõe como

  • sed -n '/PAT1/,/PAT2/ – localiza o intervalo entre PAT1 e PAT2 e suprime a impressão;

  • /PAT1/{n}; – se corresponder a PAT1 mova para n (seguinte) linha;

  • /PAT2/{d}; – se corresponder PAT2 linha de exclusão do PAT2 ;

  • p – imprime todas as linhas que caíram em /PAT1/,/PAT2/ e não foram ignoradas ou excluídas.

Alternativamente:

 sed '/START/,/END/!d;//d' 

Isso exclui todas as linhas, exceto aquelas entre START e END, e então //d deleta as linhas START e END, já que // faz com que sed use os padrões anteriores.