Saltar al contenido

Cómo grep-inverse-match y excluir líneas “antes” y “después”

Solución:

Podrías usar gnu grep con -A y -B para imprimir exactamente las partes del archivo que desea excluir, pero agregue el -n cambie para imprimir también los números de línea y luego formatee la salida y páselo como un script de comando a sed para borrar esas líneas:

grep -n -A1 -B2 PATTERN infile | 
sed -n 's/^([0-9]{1,}).*/1d/p' | 
sed -f - infile

Esto también debería funcionar con archivos de patrones pasados ​​a grep vía -f p.ej:

grep -n -A1 -B2 -f patterns infile | 
sed -n 's/^([0-9]{1,}).*/1d/p' | 
sed -f - infile

Creo que esto podría optimizarse ligeramente si colapsara tres o más números de línea consecutivos en rangos para tener, por ejemplo, 2,6d en lugar de 2d;3d;4d;5d;6d… aunque si la entrada tiene solo unas pocas coincidencias, no vale la pena hacerlo.


Otras formas que no conservan el orden de las líneas y que probablemente sean más lentas:
con comm:

comm -13 <(grep PATTERN -A1 -B2 <(nl -ba -nrz -s: infile) | sort) 
<(nl -ba -nrz -s: infile | sort) | cut -d: -f2-

comm requiere una entrada ordenada, lo que significa que el orden de las líneas no se conservará en la salida final (a menos que su archivo ya esté ordenado), por lo que nl se utiliza para numerar las líneas antes de ordenar, comm -13 imprime solo líneas exclusivas de 2do ARCHIVO y luego cut elimina la parte que fue agregada por nl (es decir, el primer campo y el delimitador :)
con join:

join -t: -j1 -v1 <(nl -ba -nrz -s:  infile | sort) 
<(grep PATTERN -A1 -B2 <(nl -ba -nrz -s:  infile) | sort) | cut -d: -f2-

don’s podría ser mejor en la mayoría de los casos, pero solo en caso de que el archivo sea De Verdad grande, y no puedes conseguir sed para manejar un archivo de script tan grande (lo que puede suceder en alrededor de 5000+ líneas de script), aquí está con llano sed:

sed -ne:t -e"/n.*$match/D" 
    -e'$!N;//D;/'"$match/{" 
            -e"s/n/&/$A;t" 
            -e'$q;bt' -e}  
    -e's/n/&/'"$B;tP"      
    -e'$!bt' -e:P  -e'P;D'

Este es un ejemplo de lo que se llama ventana deslizante en la entrada. Funciona construyendo un mirar hacia el futuro búfer de $B-contar líneas antes de intentar imprimir nada.

Y, de hecho, probablemente debería aclarar mi punto anterior: el limitador de rendimiento principal tanto para esta solución como para Don estará directamente relacionado con el intervalo. Esta solución se ralentizará con un intervalo mayor tamaños, mientras que los de Don se ralentizarán con un intervalo mayor frecuencias. En otras palabras, incluso si el archivo de entrada es muy grande, si la ocurrencia del intervalo real sigue siendo muy poco frecuente, entonces su solución probablemente sea el camino a seguir. Sin embargo, si el tamaño del intervalo es relativamente manejable y es probable que ocurra con frecuencia, esta es la solución que debe elegir.

Así que aquí está el flujo de trabajo:

  • Si $match se encuentra en el espacio patrón precedido por un newline, sed será recursivamente Delegir cada newline que lo precede.
    • Yo despejaba $matchEl espacio del patrón se ha eliminado completamente antes, pero para manejar fácilmente la superposición, dejar un punto de referencia parece funcionar mucho mejor.
    • Yo tambien probé s/.*n.*($match)/1/ para intentar conseguirlo de una vez y esquivar el bucle, pero cuando $A/$B son grandes, los Delete loop resulta considerablemente más rápido.
  • Luego tiramos del Next línea de entrada precedida por un newline delimitador y vuelva a intentar Delete un /n.*$match/ una vez más haciendo referencia a nuestra expresión regular utilizada más recientemente con //.
  • Si el espacio del patrón coincide $match entonces solo puede hacerlo con $match a la cabeza de la fila – todos $BAntes de que se hayan despejado las líneas.
    • Entonces comenzamos a recorrer $Adespués.
    • Cada ejecución de este bucle intentaremos s///sustituto de &en sí mismo el $Ath newline en el espacio del patrón y, si tiene éxito, test nos ramificará – y todo nuestro $ADespués del búfer: fuera del script por completo para iniciar el script desde arriba con la siguiente línea de entrada, si la hubiera.
    • Si el test no tiene éxito nosotros brancho de regreso al :top etiqueta y recurse para otra línea de entrada, posiblemente comenzando el bucle si $match ocurre mientras se reúne $Adespués.
  • Si pasamos un $match bucle de función, luego intentaremos print el $última línea si es así, y si !no intentar s///sustituto de &en sí mismo el $Bth ncarácter ewline en el espacio del patrón.
    • Bien tEsto también, y si tiene éxito nos ramificaremos a la :Print etiqueta.
    • Si no, volveremos a :top y obtener otra línea de entrada adjunta al búfer.
  • Si llegamos a :Print vamos Print entonces Delete hasta el primero newline en el espacio del patrón y vuelva a ejecutar el script desde la parte superior con lo que queda.

Y esta vez, si estuviéramos haciendo A=2 B=2 match=5; seq 5 | sed...

El espacio del patrón para la primera iteración en :Print se vería así:

^1n2n3$

Y asi es como sed reúne su $Bantes de búfer. Y entonces sed imprime a la salida $B-líneas de conteo detrás la entrada que ha recopilado. Esto significa que, dado nuestro ejemplo anterior, sed haría Print 1 a la salida, y luego Delija eso y envíe de vuelta a la parte superior del script un espacio de patrón que se parece a:

^2n3$

… y en la parte superior del guión el NSe recupera la línea de entrada ext y, por lo tanto, la siguiente iteración se ve así:

^2n3n4$

Y así, cuando encontramos la primera aparición de 5 en la entrada, el espacio del patrón en realidad se ve así:

^3n4n5$

Entonces el DEl bucle elete se activa y cuando se completa se ve así:

^5$

Y cuando el Nse tira de la línea de entrada ext sed golpea EOF y se cierra. Para ese momento solo ha Plíneas impresas 1 y 2.

Aquí hay un ejemplo de ejecución:

A=8 B=7 match="[24689]0"
seq 100 |
sed -ne:t -e"/n.*$match/D" 
    -e'$!N;//D;/'"$match/{" 
            -e"s/n/&/$A;t" 
            -e'$q;bt' -e}  
    -e's/n/&/'"$B;tP"      
    -e'$!bt' -e:P  -e'P;D'

Eso imprime:

1
2
3
4
5
6
7
8
9
10
11
12
29
30
31
32
49
50
51
52
69
70
71
72
99
100

Si no te importa usar vim:

$ export PAT=fff A=1 B=2
$ vim -Nes "+g/${PAT}/.-${B},.+${A}d" '+w !tee' '+q!' foo
aaa
bbb
ccc
hhh
iii
  • -Nes activa el modo ex silencioso no compatible. Útil para la creación de scripts.
  • +{command} dile a vim que corra {command} en el archivo.
  • g/${PAT}/ – en todas las líneas coincidentes /fff/. Esto se vuelve complicado si el patrón contiene caracteres especiales de expresión regular que no pretendía tratar de esa manera.
  • .-${B} – de 1 línea por encima de esta
  • .+${A} – a 2 líneas debajo de ésta (ver :he cmdline-ranges para estos dos)
  • d – eliminar las líneas.
  • +w !tee luego escribe en la salida estándar.
  • +q! se cierra sin guardar los cambios.

Puede omitir las variables y usar el patrón y los números directamente. Los usé solo por claridad de propósito.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *