Encontre correspondências em linhas adjacentes

3

Eu quero encontrar linhas de correspondência adjacentes, por exemplo, se o padrão corresponder a

$ grep -n pattern file1 file2 file3
file1:10: ...
file2:100: ...
file2:1000: ...
file2:1001: ...
file3:1: ...
file3:123: ...

Eu quero encontrar as duas combinações intermediárias:

file2:1000: ...
file2:1001: ...

mas não os dois primeiros e os dois últimos.

    
por sds 22.04.2016 / 20:44

4 respostas

2

Vou usar o mesmo arquivo de teste que o thrig:

$ cat file
a
pat 1
pat 2
b
pat 3

Aqui está uma solução para o awk:

$ awk '/pat/ && last {print last; print} {last=""} /pat/{last=$0}' file
pat 1
pat 2

Como funciona

awk implicitamente percorre todas as linhas do arquivo. Este programa usa uma variável, last , que contém a última linha se corresponder a regex pat . Caso contrário, ele contém a string vazia.

  • /pat/ && last {print last; print}

    Se pat corresponder a essa linha e a linha anterior, last , também corresponder, imprima as duas linhas.

  • {last=""}

    Substitua last por uma string vazia

  • /pat/ {last=$0}

    Se esta linha corresponder a pat , defina last para esta linha. Desta forma, estará disponível quando processarmos a próxima linha.

Alternativa para tratar > 2 correspondências consecutivas como um grupo

Vamos considerar este arquivo de teste estendido:

$ cat file2
a
pat 1
pat 2
b
pat 3
c
pat 4
pat 5
pat 6
d

Diferentemente da solução acima, esse código trata as três linhas de correspondência consecutivas como um grupo a ser impresso:

$ awk '/pat/{f++; if (f==2) print last; if (f>=2) print; last=$0; next} {f=0}' file2
pat 1
pat 2
pat 4
pat 5
pat 6

Este código usa duas variáveis. Como antes, last é a linha anterior. Além disso, f conta o número de correspondências consecutivas. Assim, imprimimos linhas de correspondência quando f é 2 ou maior.

Adicionando recursos semelhantes ao grep

Para emular a saída grep mostrada na pergunta, esta versão imprime o nome do arquivo e o número da linha antes de cada linha correspondente:

$ awk 'FNR==1{f=0} /pat/{f++; if (f==2) printf "%s:%s:%s\n",FILENAME,FNR-1,last; if (f>=2) printf "%s:%s:%s\n",FILENAME,FNR,$0; last=$0; next} {f=0}' file file2
file:2:pat 1
file:3:pat 2
file2:2:pat 1
file2:3:pat 2
file2:7:pat 4
file2:8:pat 5
file2:9:pat 6

As variáveis FILENAME de Awk fornecem o nome do arquivo e o FNR do awk fornece o número da linha dentro do arquivo.

No início de cada arquivo, FNR==1 , redefinimos f para zero. Isso impede que a última linha de um arquivo seja considerada consecutiva com a primeira linha do próximo arquivo.

Para quem gosta do código espalhado por várias linhas, o texto acima é semelhante:

awk '
    FNR==1{f=0}
    /pat/ {f++
        if (f==2) printf "%s:%s:%s\n",FILENAME,FNR-1,last
        if (f>=2) printf "%s:%s:%s\n",FILENAME,FNR,$0
        last=$0
        next
    }

    {f=0}
    ' file file2
    
por 23.04.2016 / 00:50
2

Uma forma seria salvar a linha anterior e imprimir quando a linha atual e a anterior coincidirem:

bash-4.1$ (echo a; echo pat 1; echo pat 2; echo b; echo pat 3)
a
pat 1
pat 2
b
pat 3
bash-4.1$ (echo a; echo pat 1; echo pat 2; echo b; echo pat 3) | \
          perl -nle 'print "$prev\n$_" if /pat/ and $prev =~ /pat/; $prev=$_'
pat 1
pat 2

Isso, no entanto, resultará em correspondências duplicadas, caso haja três ou mais linhas adjacentes que correspondam, pois elas corresponderão em pares duas ou mais vezes. Uma opção melhor seria manter o controle do número de linhas anteriores que correspondem e também escrever algum código de teste para confirmar que os vários casos complicados de borda (por exemplo, um bloco contra o final do arquivo) são tratados corretamente.

#!/usr/bin/env perl
use strict;
use warnings;

my $prev;
my $pattern = qr/pat/;
my $have_matches = 0;

while (my $line = readline) {
  if ($line =~ /$pattern/) {
    print $prev if $have_matches == 1;
    print $line if $have_matches;
    $have_matches++;
    $prev = $line;
  } else {
    $have_matches = 0;
  }
}
    
por 22.04.2016 / 22:38
1

Para o registro, você também pode fazer isso com sed :

sed -s '$!N
/.*PATTERN.*\n/{/\n.*PATTERN/{x;/^1$/!s/.*/1/;b v};//!{x;/^1$/{s/./0/;b v};//!D}}
//!{${/PATTERN/{x;/^1$/{b v}}};D;};: v;x;P;D' file1 file2 ... fileN

Isso é gnu sed . Com outros sed s você teria que processar um arquivo de cada vez:

sed '$!N                   # if not on the last line pull in the next line
/.*PATTERN.*\n/{           # if first line in the pattern space matches
/\n.*PATTERN/{             # and if second line also matches                   
x                          # exchange pattern space with hold buffer
/^1$/!s/.*/1/              # replace everything with 1
b v                        # branch to label v
}
//!{                       # if second line does not match
x                          # exchange pattern space with hold buffer
/^1$/{                     # if it matches 1
s/.*/0/                    # replace with 0
b v                        # branch to label v
}
//!D                       # if it does not match 1 delete up to first newline
}
}
//!{                       # if first line does not match
${                         # if we're on the last line
/PATTERN/{                 # and if it matches
x                          # exchange pattern space with hold buffer
/^1$/{                     # if it matches 1
b v                        # branch to label v
}
}
}
D                          # else delete up to first newline
}
: v                        # label v
x                          # exchange pattern space with hold buffer
P                          # print up to first newline
D' infile                  # delete up to first newline

Não é tão flexível quanto perl ou awk - você não pode emular totalmente grep output, isto é, prefixo de linhas com o nome do arquivo e número da linha, embora com gnu sed você pode obter o nome do arquivo adicionando F antes do P e, em seguida, canalizar toda a saída para paste -d: - -

    
por 26.04.2016 / 14:03
-2

Olá, existem vários comandos que podem ajudá-lo a fazer a última linha.

<grep command> | tail -1

ou

awk '/result/ { save=$0 }END{ print save }' filename
    
por 22.04.2016 / 20:59