shell - Como imprimir linhas entre dois padrões, inclusive ou exclusivo(em sed, AWK ou Perl)?




pattern-matching (5)

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 variáveis:

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, imprimirá todas as linhas que ocorrem desde o tempo em que o PAT1 ocorre 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 removida pelo reordenamento 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 o PAT1 e o PAT2 - incluindo o PAT2

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

Ao colocar 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.

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, incluindo ou excluindo o padrão.

Como posso imprimir todas as linhas entre dois padrões?


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.


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

Linhas de impressão entre PAT1 e PAT2 - incluem PAT1 e PAT2

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

Imprimir linhas entre PAT1 e PAT2 - excluir PAT1 e PAT2

Sed GNU
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
Qualquer sed 1
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p;};}' FILE

ou até mesmo (Obrigado Sundeep ):

Sed GNU
sed -n '/PAT1/,/PAT2/{//!p}' FILE
Qualquer sed
sed -n '/PAT1/,/PAT2/{//!p;}' FILE

Linhas de impressão entre PAT1 e PAT2 - incluem PAT1 mas não PAT2

O seguinte inclui apenas o início do intervalo:

Sed GNU
sed -n '/PAT1/,/PAT2/{/PAT2/!p}' FILE
Qualquer sed
sed -n '/PAT1/,/PAT2/{/PAT2/!p;}' FILE

Linhas de impressão entre PAT1 e PAT2 - incluem PAT2 mas não PAT1

O seguinte inclui apenas o final do intervalo:

Sed GNU
sed -n '/PAT1/,/PAT2/{/PAT1/!p}' FILE
Qualquer sed
sed -n '/PAT1/,/PAT2/{/PAT1/!p;}' FILE

1 Nota sobre BSD / Mac OS X sed

Um comando como este aqui:

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

Emitiria um erro:

▶ sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
sed: 1: "/PAT1/,/PAT2/{/PAT1/!{/ ...": extra characters at the end of p command

Por esta razão, esta resposta foi editada para incluir as versões BSD e GNU dos one-liners.


Para completar, aqui está uma solução Perl:

Linhas de impressão entre PAT1 e PAT2 - incluem PAT1 e PAT2

perl -ne '/PAT1/../PAT2/ and print' FILE

ou:

perl -ne 'print if /PAT1/../PAT2/' FILE

Imprimir linhas entre PAT1 e PAT2 - excluir PAT1 e PAT2

perl -ne '/PAT1/../PAT2/ and !/PAT1/ and !/PAT2/ and print' FILE

ou:

perl -ne 'if (/PAT1/../PAT2/) {print unless /PAT1/ or /PAT2/}' FILE 

Imprimir linhas entre PAT1 e PAT2 - excluir somente PAT1

perl -ne '/PAT1/../PAT2/ and !/PAT1/ and print' FILE

Imprimir linhas entre o PAT1 e o PAT2 - excluir apenas o PAT2

perl -ne '/PAT1/../PAT2/ and !/PAT2/ and print' FILE

Veja também:

  • Seção de operador de intervalo em perldoc perlop para mais sobre a gramática /PAT1/../PAT2/ :

Operador de faixa

... No contexto escalar, ".." retorna um valor booleano. O operador é biestável, como um flip-flop, e emula o operador de intervalo de linha (vírgula) de sed, awk e vários editores.

  • Para a opção -n , veja perldoc perlrun , que faz o Perl se comportar como sed -n .

  • Perl Cookbook, 6.8 para uma discussão detalhada de extração de um intervalo de linhas.


Você pode fazer o que quiser com sed , suprimindo a impressão normal do espaço padrão com -n . Por exemplo, para incluir 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 dentro de /PAT1/,/PAT2/ e não foram ignoradas ou excluídas.







pattern-matching