Encuentra todas las ocurrencias en un archivo con sed

15

Usando OPEN STEP 4.2 OS ... Actualmente estoy usando el siguiente sedcomando:

sed -n '1,/141.299.99.1/p' TESTFILE | tail -3

Este comando encontrará una instancia en un archivo con la ip de 141.299.99.1 y también incluirá 3 líneas antes, lo cual es bueno, con la excepción de que también me gustaría encontrar todas las instancias de la IP y las 3 líneas anteriores. y no solo el primero.

Valle
fuente
1
Por favor, siempre incluya su sistema operativo. Las soluciones a menudo dependen del sistema operativo utilizado. ¿Estás utilizando Unix, Linux, BSD, OSX, algo más? ¿Cual version?
terdon
¡GRAN PUNTO! El uso de Open Step versión 4.2 es bastante antiguo y los shells incluidos no incluyen muchas de las características mencionadas en las respuestas a continuación.
Dale
Por curiosidad: ¿qué es un sistema OPEN STEP 4.2 y para qué se utiliza hoy en día?
Thorbjørn Ravn Andersen
(y si Perl está disponible, realmente puedes hacer muchas cosas buenas con eso)
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Quizás sea esto: en.wikipedia.org/wiki/OpenStep
Barmar

Respuestas:

4

Aquí hay un intento de emular grep -B3usando una ventana de movimiento de sed, basado en este ejemplo de GNU sed (pero con suerte compatible con POSIX, con reconocimiento a @ StéphaneChazelas):

sed -e '1h;2,4{;H;g;}' -e '1,3d' -e '/141\.299\.99\.1/P' -e '$!N;D' file

Las dos primeras expresiones preparan un búfer de patrón de varias líneas y le permiten manejar el caso de borde en el que hay menos de 3 líneas de contexto anterior antes de la primera coincidencia. La expresión del medio (coincidencia de expresiones regulares) imprime una línea desde la parte superior de la ventana hasta que el texto de coincidencia deseado se ondula a través del búfer de patrón. El final $!N;Ddesplaza la ventana una línea, excepto cuando llega al final de la entrada.

conductor de acero
fuente
-eno es específico de GNU. Para ser POSIX / portátil, lo necesita ya que no puede haber nada después }(y necesita un ;antes).
Stéphane Chazelas
Gracias @ StéphaneChazelas, entonces, ¿estás diciendo que para ser POSIX / portátil, el primer grupo debe dividirse / modificarse como -e '1h;2,4{H;g;}' -e '1,3d'? No tengo un sistema que no sea GNU para probar (y al --posixinterruptor sed de GNU no parece importarle).
steeldriver
1
Sí, en Linux, puede probar una implementación diferente con el seddel heredero de herramientas de herencia que es un descendiente de la tradicional Unix sed. La especificación POSIX / Unix sedestá en pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
Stéphane Chazelas
Recibo un evento no encontrado en ninguno de estos: N; D ': evento no encontrado. ¿Me falta la sintaxis en alguna parte? ¡¡Gracias!!
Dale
Lo siento, me acabo de dar cuenta de que mi edición más reciente omitió una comilla simple de cierre después de la primera expresión -e. Lo he corregido ahora. ¿Puedes intentarlo de nuevo con la expresión anterior, por favor?
steeldriver
10

grep hará un mejor trabajo de esto:

grep -B 3 141.299.99.1 TESTFILE

Los -B 3medios para imprimir las tres líneas antes de cada partido. Esto se imprimirá --entre cada grupo de líneas. Para deshabilitar eso, úsalo --no-group-separatortambién.

La -Bopción es compatible con GNUgrep y la mayoría de las versiones BSD también ( OSX , FreeBSD , OpenBSD , NetBSD ), pero técnicamente no es una opción estándar.

Michael Homer
fuente
1
Michael Homer - Gracias No tengo la opción - B. ¿Alguna otra idea?
Dale
@Dale ¿Puedes instalar GNU grep? Eso te dará la opción.
Barmar
9

Con sedusted puede hacer una ventana deslizante.

sed '1N;$!N;/141.299.99.1/P;D'

Eso lo hace. ¡Pero cuidado - bashel comportamiento loco de expandirse ! incluso cuando se cita! en la cadena de comandos de su historial de comandos puede hacer que se vuelva un poco loco. Prefije el comando con set +H;si encuentra que este es el caso. Para luego volver a habilitarlo (pero ¿por qué ???) hazlo set -Hdespués.

Eso, por supuesto, solo se aplicaría si estuviera usando bash, aunque no creo que lo esté haciendo. Estoy bastante seguro de que está trabajando con csh- (que resulta ser el shell cuyo comportamiento loco bashemula con la expansión del historial, pero tal vez no en los extremos que el shell c lo llevó) . Así que , probablemente, una \!debería funcionar. Espero.

Todo es código portátil: POSIX describe sus tres operadores de la siguiente manera: (aunque vale la pena señalar que solo he confirmado que esta descripción existía ya en 2001)

[2addr]N Agregue la siguiente línea de entrada, menos su línea de \new final , al espacio del patrón, usando una \nlínea de ew incrustada para separar el material adjunto del material original. Tenga en cuenta que el número de línea actual cambia.

[2addr]P Escriba el espacio del patrón, hasta el primer \newline, en la salida estándar.

[2addr]D Elimine el segmento inicial del espacio del patrón a través de la primera línea \new y comience el siguiente ciclo.

Entonces, en la primera línea, agrega una línea adicional al espacio del patrón, para que se vea así:

^line 1s contents\nline 2s contents$

A continuación, en la primera línea y cada línea a partir de entonces - con excepción de la última - se agrega otra línea al espacio de patrones. Entonces se ve así:

^line 1\nline 2\nline 3$

Si su dirección IP se encuentra dentro de usted, Pdiríjase a la primera línea nueva, así que solo la línea 1 aquí. Al final de cada ciclo, Deliges lo mismo y comienzas de nuevo con lo que queda. Entonces el siguiente ciclo se ve así:

^line 2\nline 3\nline 4$

...y así. Si su IP se encuentra en cualquiera de esos tres, se imprimirá la más antigua cada vez. Entonces siempre estás solo tres líneas por delante.

Aquí hay un ejemplo rápido. Obtendré un búfer de tres líneas impreso para cada número que termine en cero:

seq 10 52 | sed '1N;$!N;/0\(\n\|$\)/P;D'

10
18
19
20
28
29
30
38
39
40
48
49
50

Eso es un poco más complicado que su caso porque tuve que alternar desde la 0\nnueva línea o el 0$final del espacio del patrón para parecerse más a su problema, pero son sutilmente diferentes ya que esto requiere un ancla, lo que puede ser un poco difícil de hacer ya que el espacio de patrones cambia constantemente.

Utilicé los casos impares de 10 y 52 para mostrar que mientras el ancla sea flexible, también lo será la salida. Totalmente portátil, puedo lograr los mismos resultados al contar con el algoritmo y hacer:

seq 10 52 | sed '1N;$!N;/[90]\n/P;D'

Y ampliar la búsqueda al tiempo que restringe mi ventana: de 0 a 9 y 0 y de 3 líneas a dos.

De todos modos, entiendes la idea.

mikeserv
fuente
Gracias por todo su trabajo duro. Lo sentimos, ¿dónde pondría el nombre del archivo que me gustaría buscar?
Dale
@Dale - mi mal. sed '...' $filename. Por cierto, dejé los períodos de su propia cadena de búsqueda, pero esos no son realmente períodos en un patrón, representan cualquier carácter individual. Probablemente deberías hacer oct\.oct\.oct\.octpara escapar de ellos para que solo coincidan con los períodos.
mikeserv
Traté de utilizarlo con diferentes símbolos <> y obtengo un evento no encontrado que obtengo con otras soluciones aquí, así que me pregunto si mi sistema operativo no es compatible con estas soluciones.
Dale
ahora resulta con -> N; /141.299.99.1/P; D ': Evento no encontrado.
Dale
@Dale: consulte la actualización. Te debería ayudar.
mikeserv
4

Como mencionas que no tienes la -Bopción grep, puedes usar Perl (por ejemplo) para hacer una ventana deslizante de 4 líneas:

perl -ne '
    push @window,$_;
    shift @window if @window > 4;
    print @window if /141\.299\.99\.1/
' your_file

La respuesta de Ramesh hace algo similar con awk.

Joseph R.
fuente
No estoy seguro de si mi versión de Perl lo admite, pero lo intentaré. Muchas gracias por tomarse el tiempo para responder mi pregunta, ¡muy agradecido!
Dale
@Dale De nada. Dudo que este código haga uso de cualquier característica de Perl de vanguardia.
Joseph R.
4

Cuando esté disponible, puede usar pcregrep :

pcregrep -M '.*\n.*\n.*\n141.299.99.1' file
caos
fuente
Comprobando si tengo PCREGREP. Me gusta la compacidad del comando. Muy agradecido por su tiempo y esfuerzo. ¡¡¡Gracias!!!
Dale
4

Puede implementar el mismo enfoque básico que las otras respuestas no grep en el propio shell (esto supone un shell relativamente reciente que admite =~):

while IFS= read -r line; do 
    [[ $line =~ 141.299.99.1 ]] && printf "%s\n%s\n%s\n%s\n" $a $b $c $line;
    a=$b; b=$c; c=$line; 
done < file 

Alternativamente, puede sorber todo el archivo en una matriz:

perl -e '@F=<>; 
        for($i=0;$i<=$#F;$i++){
          print $F[$i-3],$F[$i-2],$F[$i-1],$F[$i] if $F[$i]=~/141.299.99.1/
        }' file 
terdon
fuente
Mi caparazón es muy viejo: Steve Jobs Open Step. ¡Gran idea y gracias por tu tiempo! Dale
Dale
@Dale, el enfoque perl funcionará en casi cualquier lugar. Díganos su sistema operativo (agréguelo a su pregunta) de esa manera podemos sugerirle cosas que funcionarán para usted.
terdon
Si copio su Perl y lo pongo en NotePad y lo pongo en una línea, ¡funciona! Pregunta: si quisiera, digamos 10 líneas antes del patrón de coincidencia, ¿dónde cambiaría el 3 a 10? ¡Gracias!
Dale
Veo que puedo agregar más líneas agregando más $ F [$ iX], declaraciones. ¡Gracias!
Dale
4

Si su sistema no es compatible con el grepcontexto, puede probar ack-grep en su lugar:

ack -B 3 141.299.99.1 file

ack es una herramienta como grep, optimizada para programadores.

Cuonglm
fuente
Me gusta la compacidad del comando, pero mi sistema no admite la búsqueda en las páginas del manual. ¡Gran idea y muchas gracias por su tiempo! Dale
Dale
@Dale: ¡Sorprendente! ¿Cuál es tu sistema operativo? Si tienes perl, puedes usar ack.
cuonglm
2
awk '/141.299.99.1/{for(i=1;i<=x;)print a[i++];print} {for(i=1;i<x;i++)
     a[i]=a[i+1];a[x]=$0;}'  x=3 filename

En esta awksolución, se usa una matriz que siempre contendrá 3 líneas antes del patrón actual. Por lo tanto, cuando el patrón coincide, se imprime el contenido de la matriz junto con el patrón actual.

Pruebas

-bash-3.2$ cat filename
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.5
10.0.0.6
10.0.0.7
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.11
10.0.0.12
10.0.0.13
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
10.0.0.17
10.0.0.18
10.0.0.19

Después de ejecutar el comando, la salida es,

10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
Ramesh
fuente
tan detallado, muchas gracias. Lo probaré. Muy agradecido por tu tiempo !! Dale
Dale
¡Tengo un archivo de prueba y su solución funciona! Sin embargo, el problema es que cuando lo ejecuto en mi archivo de producción grande, vuelve con un número de registro demasiado largo, por lo que la salida no puede funcionar con el comando. Mi comando original en la parte superior de esta página funciona pero solo encuentra una instancia. Aprecio tu ayuda. ¿Hay algo que pueda hacer con mi comando original para que encuentre más de una instancia?
Dale
1

En la mayoría de estos, /141.299.99.1/también coincidirá (por ejemplo) 141a299q99+1o 141029969951porque .en una expresión regular puede representar cualquier carácter.

El uso /141[.]299[.]99[.]1/es más seguro, y se puede añadir un contexto adicional al principio y al final de toda la expresión regular para asegurarse de que no coincide 3141., .12, .104, etc.

usuario117529
fuente
1
Este es un buen punto, y también lo consideré. Aún así, utilicé la cadena proporcionada por el autor de la pregunta como una coincidencia de trabajo conocida, y le notifiqué personalmente lo mismo cuando tuve la oportunidad. De todos modos, no todos estos, la respuesta de Steeldriver ha citado la partida de char desde el principio.
mikeserv