Como obter a última ocorrência de linhas entre dois padrões de um arquivo?

2

Eu tenho um arquivo de log que informa sobre a saída de um processo, eu gostaria de extrair todas as linhas entre a última ocorrência de dois padrões.

Os padrões serão ao longo das linhas de

Summary process started at <datestring>

e

Summary process finished at <datestring> with return code <num>

Haverá várias instâncias desses padrões em todo o arquivo, junto com muitas outras informações. Gostaria de imprimir apenas a última ocorrência.

Eu sei que posso usar:

sed -n '/StartPattern/,/EndPattern/p' FileName

Para obter linhas entre os padrões, mas não sabe como obter a última instância. Sed ou soluções awk estariam bem.

Editar: Eu não tenho sido claro sobre o comportamento que eu quero quando vários StartPatterns aparecem sem EndPattern, ou se não há EndPattern antes do final do arquivo, depois de detectar um StartPattern

Para vários StartPatterns com EndPattern ausente, gostaria apenas de linhas do último StartPattern até o EndPattern.

Para um StartPattern que atinge o EOF sem um EndPattern, eu gostaria de tudo até o EOF, seguido por inserir uma string para avisar que o EOF foi atingido.

    
por Arronical 14.06.2016 / 12:01

2 respostas

4

Você sempre pode fazer:

tac < fileName | sed  '/EndPattern/,$!d;/StartPattern/q' | tac

Se o seu sistema não tiver o GNU tac , você poderá usar tail -r .

Você também pode fazer como:

awk '
  inside {
    text = text $0 RS
    if (/EndPattern/) inside=0
    next
  }
  /StartPattern/ {
    inside = 1
    text = $0 RS
  }
  END {printf "%s", text}' < filename

Mas isso significa ler todo o arquivo.

Observe que pode haver resultados diferentes se houver outro StartPattern entre um StartPattern e o próximo EndPattern ou se o último StartPattern não tiver um final EndPattern ou se houver linhas correspondentes aos dois StartPattern e EndPattern .

awk '
  /StartPattern/ {
    inside = 1
    text = ""
  }
  inside {text = text $0 RS}
  /EndPattern/ {inside = 0} 
  END {printf "%s", text}' < filename

Faria isso se comportar mais como a abordagem tac+sed+tac (exceto pelo caso não divulgado StartPattern ).

Esse último parece ser o mais próximo dos seus requisitos editados. Para adicionar o aviso seria simplesmente:

awk '
  /StartPattern/ {
    inside = 1
    text = ""
  }
  inside {text = text $0 RS}
  /EndPattern/ {inside = 0} 
  END {
    printf "%s", text
    if (inside)
      print "Warning: EOF reached without seeing the end pattern" > "/dev/stderr"
  }' < filename

Para evitar a leitura do arquivo inteiro:

tac < filename | awk '
  /StartPattern/ {
    printf "%s", $0 RS text
    if (!inside)
      print "Warning: EOF reached without seeing the end pattern" > "/dev/stderr"
    exit
  }
  /EndPattern/ {inside = 1; text = ""}
  {text = $0 RS text}'

Nota de portabilidade: para /dev/stderr , você precisa de um sistema com um arquivo especial (cuidado com o Linux se o stderr estiver aberto em um arquivo pesquisável que gravará o texto no início do arquivo em vez da posição atual dentro do arquivo) ou uma implementação de awk que o emula como gawk , mawk ou busybox awk (os trabalhos em torno do problema do Linux mencionado acima).

Em outros sistemas, você pode substituir print ... > "/dev/stderr" por print ... | "cat>&2" .

    
por 14.06.2016 / 12:24
3

Você pode usar o GNU sed da mesma forma

sed '/START/{:1;$!{/END/!{N;b1};h}};${x;p};d' file

Apenas sobrescreve o espaço de espera em todas as ocorrências do padrão multilinha completo. Imprime no final do arquivo.

Isso fornecerá um comportamento consistente, como

  • Tanto o START quanto o END estão na mesma linha, corresponderão à linha.
  • Múltiplos STARTs após o START inicial, corresponderão a todos até END
  • A correspondência não será impressa se não houver END, será impressa a última ocorrência de START para END
por 14.06.2016 / 12:52