Imprima la enésima línea antes de la línea coincidente, la línea coincidente y la enésima línea desde la línea coincidente

Imprima la enésima línea antes de la línea coincidente, la línea coincidente y la enésima línea desde la línea coincidente

Quiero imprimir la enésima línea antes de la línea coincidente, la línea coincidente y la enésima línea de la línea coincidente donde "n" es mayor que 2.

Aquí hay un ejemplo de mi archivo de datos (los números de línea a continuación no son parte de los datos y solo sirven para identificación). El patrón que estoy buscando es "bla" en el example.txtarchivo.

$ cat example.txt 
 1. a
 2. b
 3. c
 4. d
 5. blah
 6. e
 7. f
 8. g
 9. h
 10. blah
 11. i
 12. f
 13. g
 14. h

Y quiero la salida como:

 1. b
 2. blah
 3. g
 4. f
 5. blah
 6. g

¡Sugiera cualquier revestimiento!

Respuesta1

awk -vn=3 '/blah/{print l[NR%n];print;p[NR+n]};(NR in p);{l[NR%n]=$0}'

Eso supone que no hay superposición. Si hay superposición, se imprimirán todas las líneas relevantes, pero posiblemente varias veces y no necesariamente en el mismo orden que en la entrada.

Para evitar eso, podrías escribirlo como:

awk -vn=3 '/blah/{p[NR-n]p[NR]p[NR+n]};(NR-n in p){print l[NR%n]}
  {l[NR%n]=$0};END{for(i=NR-n+1;i<=NR;i++)if (i in p) print l[i%n]}'

En una entrada como:

1
2
3
4
blah1
5
6
blah2
blah3
7
8
9
10

El primero daría:

2
blah1
blah1
blah2
blah2
5
blah3
8
9

Mientras que el segundo imprimiría:

2
blah1
5
blah2
blah3
8
9

Respuesta2

Aquí hay una frase de Perl:

$ perl -ne '$n=3;push @lines,$_; END{for($i=0;$i<=$#lines;$i++){
  if ($lines[$i]=~/blah/){
    print $lines[$i-$n],$lines[$i],$lines[$i+$n]}}
 }' example.txt 
b
blah
g
f
blah
g

Para cambiar el número de líneas circundantes, cambie $n=3;a $n=Ndonde Nestá el número deseado. Para cambiar el patrón coincidente, cambie if ($lines[$i]=~/blah/)a if ($lines[$i]=~/PATTERN/).

Si los números son realmente parte del archivo, puedes hacer algo como esto:

$ perl -ne '$n=3;push @lines,$_; END{for($i=0;$i<=$#lines;$i++){
      if ($lines[$i]=~/blah/){
        print $lines[$i-$n],$lines[$i],$lines[$i+$n]}}
     }' example.txt | perl -pne 's/\d+/$./'
1. b
2. blah
3. g
4. f
5. blah
6. g

Respuesta3

Aquí hay una respuesta similar a la de @terdon, pero solo mantiene las 2n+1 líneas relevantes en la memoria:

my $n = shift;
my $pattern = shift;
my @lines = ("\n") x (2*$n+1);
while (<>) {
    shift @lines;
    push @lines, $_;
    if ($lines[$n] =~ m/$pattern/) {
        print $lines[0], $lines[$n], $lines[-1];
    }
}

Y lo ejecutarías como:perl example.pl 3 blah example.txt

Respuesta4

No es terriblemente eficiente. Tome números de línea usando grep, imprima números de línea usando sed.

for n in `grep -n blah example.txt | sed -e s/:.*//`
do
    sed -n -e "$[$n-3]p" -e "$[$n]p" -e "$[$n+3]p" example.txt
done

resultados en

 2. b
 5. blah
 8. g
 7. f
 10. blah
 13. g

Probablemente fracasaría si alguno de esos números terminara fuera de rango.

información relacionada