Regexp Multilinha (grep, sed, awk, perl)

3

Eu sei que o regexp de várias linhas foi discutido dezenas de vezes, mas não consigo fazer com que ele funcione com o meu padrão.

Vou tentar explicar. Eu tenho alguns arquivos de texto em um diretório. Exemplo de texto em um arquivo:

LINE OF TEXT 2
LINE OF TEXT 1
LINE OF TEXT 3

LINE OF TEXT 1
LINE OF TEXT 2
LINE OF TEXT 3

LINE OF TEXT 1
LINE OF TEXT 3

LINE OF TEXT 3
LINE OF TEXT 2
LINE OF TEXT 1

LINE OF TEXT 2
LINE OF TEXT 3

Eu quero encontrar "LINE OF TEXT 3" que vem depois de "LINE OF TEXT 2" que, por sua vez, vem depois de "LINE OF TEXT 1" (sem linhas vazias no meio).

Cada linha deve ser um próprio regexp, por exemplo, uma linha começa com "LINE" e termina com um número específico.

Nota: Nem todos os arquivos contêm essa sequência de linha exata, portanto, se houver uma correspondência de padrões, não imprima o padrão, mas apenas imprima o nome do arquivo para STDOUT.

Isso pode ser feito em um regexp de uma linha? Então, por exemplo, awk procura um padrão em um arquivo e imprime o nome do arquivo para STDOUT se um padrão for encontrado. Eu então posso usar este regexp em uma combinação com "find -exec".

Qualquer ferramenta mencionada irá (grep, awk, sed ou perl).

    
por MikZyth 23.02.2017 / 07:27

3 respostas

1

Você pode fazer isso com Awk, definindo a variável "Record Separator" como uma regex correspondente a pelo menos dois caracteres consecutivos de nova linha:

awk -v RS='\n\n+' '/1.*2.*3/' file.txt

Você também pode definir o "Separador de campo" como um único caractere de nova linha:

awk -v RS='\n\n+' -F '\n' '$1 == "LINE OF TEXT 1" && $2 == "LINE OF TEXT 2" && $3 == "LINE OF TEXT 3"' file.txt

Separado para legibilidade:

awk -v RS='\n\n+' -F '\n' '
  $1 == "LINE OF TEXT 1" &&
  $2 == "LINE OF TEXT 2" &&
  $3 == "LINE OF TEXT 3"
' file.txt

Com sua exigência de apenas imprimir o nome do arquivo, se uma correspondência for encontrada, você poderá fazer isso da seguinte forma:

awk -v RS='\n\n+' -F '\n' '
  $1 == "LINE OF TEXT 1" &&
  $2 == "LINE OF TEXT 2" &&
  $3 == "LINE OF TEXT 3" {
    match++
  }
  END {
    if (match) {
      print FILENAME
    }
' file.txt

Mas considerando que você está falando sobre usando find em combinação com awk , recomendo apenas usando o Awk para o status de saída e usando find para a impressão:

find . -type f -exec awk -v RS='\n\n+' -F '\n' '
  $1 ~ /LINE OF TEXT 1/ &&
  $2 ~ /LINE OF TEXT 2/ &&
  $3 ~ /LINE OF TEXT 3/ {
    exit 0
  }
  END { exit 1 }
' {} \; -print

Dessa forma, se você quiser fazer algo mais antes de imprimir (alguns outros find primário), você já está configurado para fazer isso.

    
por 23.02.2017 / 08:03
1

Você pode usar o "modo de parágrafo" em Perl, ele lerá o arquivo pelos blocos separados por múltiplas novas linhas. Basta definir a string vazia para o separador de registro de entrada $/ :

perl -lne 'BEGIN { $/ = "" }
       $found = 1 if /^LINE.* 1\nLINE.* 2\nLINE.* 3$/m;
       if (eof) { print $ARGV if $found; undef $found }
' -- file1 file2...
  • eof é verdadeiro no final de cada arquivo
  • $ARGV é o nome do arquivo aberto no momento.
por 23.02.2017 / 07:43
0

Você pode fazer isso usando um < - > perl duo trabalhando em conjunto, como:

find . -type f -exec \
  perl -l -0777ne '/^LINE.* 1\nLINE.* 2\nLINE.* 3$/m && print $ARGV' {} +
    
por 23.02.2017 / 10:05