¿Cómo encontrar patrones en múltiples líneas usando grep?

Quiero encontrar archivos que tengan “abc” Y “efg” en ese orden, y esas dos cadenas están en diferentes líneas en ese archivo. Ejemplo: un archivo con contenido:

blah blah.. blah blah.. blah abc blah blah blah.. blah blah.. blah blah.. blah efg blah blah blah blah.. blah blah.. 

Debería ser emparejado

Grep no es suficiente para esta operación.

pcregrep que se encuentra en la mayoría de los sistemas Linux modernos se puede usar como

 pcregrep -M 'abc.*(\n|.)*efg' test.txt 

Hay un pcre2grep más nuevo también. Ambos son proporcionados por el proyecto PCRE .

pcre2grep está disponible para Mac OS X a través de puertos Mac como parte del puerto pcre2 :

 % sudo port install pcre2 

y a través de Homebrew como:

 % brew install pcre 

No estoy seguro si es posible con grep, pero sed lo hace muy fácil:

 sed -e '/abc/,/efg/!d' [file-with-content] 

Aquí hay una solución inspirada por esta respuesta :

  • si ‘abc’ y ‘efg’ pueden estar en la misma línea:

     grep -zl 'abc.*efg'  
  • si ‘abc’ y ‘efg’ deben estar en líneas diferentes:

     grep -Pzl '(?s)abc.*\n.*efg'  

Params:

  • -z Trate la entrada como un conjunto de líneas, cada una terminada por un byte cero en lugar de una nueva línea. es decir, grep amenaza la entrada como una gran línea.

  • -l imprime el nombre de cada archivo de entrada desde el cual normalmente se imprimiría la salida.

  • (?s) active PCRE_DOTALL, lo que significa que ‘.’ encuentra cualquier personaje o nueva línea.

sed debería ser suficiente ya que el cartel LJ declaró anteriormente,

en lugar de! d puedes simplemente usar p para imprimir:

 sed -n '/abc/,/efg/p' file 

Depende mucho de pcregrep, pero con grep más nuevo no es necesario instalar pcregrep para muchas de sus características. Solo usa grep -P .

En el ejemplo de la pregunta del OP, creo que las siguientes opciones funcionan bien, con la segunda mejor coincidencia de cómo entiendo la pregunta:

 grep -Pzo "abc(.|\n)*efg" /tmp/tes* grep -Pzl "abc(.|\n)*efg" /tmp/tes* 

Copié el texto como / tmp / test1 y borré la ‘g’ y guardé como / tmp / test2. Aquí está el resultado que muestra que el primero muestra la cadena coincidente y el segundo muestra solo el nombre del archivo (típico -o es mostrar coincidencia y típico -l es mostrar solo el nombre del archivo). Tenga en cuenta que la ‘z’ es necesaria para líneas múltiples y ‘(. | \ N)’ significa que debe coincidir con ‘cualquier cosa que no sea nueva línea’ o ‘nueva línea’ – es decir, cualquier cosa:

 user@host:~$ grep -Pzo "abc(.|\n)*efg" /tmp/tes* /tmp/test1:abc blah blah blah.. blah blah.. blah blah.. blah efg user@host:~$ grep -Pzl "abc(.|\n)*efg" /tmp/tes* /tmp/test1 

Para determinar si su versión es lo suficientemente nueva, ejecute man grep y vea si algo similar a esto aparece cerca de la parte superior:

  -P, --perl-regexp Interpret PATTERN as a Perl regular expression (PCRE, see below). This is highly experimental and grep -P may warn of unimplemented features. 

Eso es de GNU grep 2.10.

Esto se puede hacer fácilmente al usar tr para reemplazar las líneas nuevas con algún otro caracter:

 tr '\n' '\a' | grep 'abc.*def' | tr '\a' '\n' 

Aquí, estoy usando el carácter de alarma, \a (ASCII 7) en lugar de una nueva línea. Esto casi nunca se encuentra en su texto, y grep puede hacer coincidirlo con a . , o unirlo específicamente con \a .

Puedes hacerlo fácilmente si puedes usar Perl.

 perl -ne 'if (/abc/) { $abc = 1; next }; print "Found in $ARGV\n" if ($abc && /efg/); }' yourfilename.txt 

También puede hacer eso con una sola expresión regular, pero eso implica tomar todo el contenido del archivo en una sola cadena, lo que podría terminar ocupando demasiada memoria con archivos grandes. Para completar, aquí está ese método:

 perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt 

No sé cómo haría eso con grep, pero haría algo como esto con awk:

 awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo 

Sin embargo, debe tener cuidado de cómo hacer esto. ¿Desea que la expresión regular coincida con la subcadena o la palabra completa? agregue tags \ w según corresponda. Además, aunque esto se ajusta estrictamente a la forma en que indica el ejemplo, no funciona cuando abc aparece una segunda vez después de efg. Si desea manejar eso, agregue un si, según corresponda, en / abc / case, etc.

awk one-liner:

 awk '/abc/,/efg/' [file-with-content] 

Lancé una alternativa de grep hace unos días que sí es compatible con esto, ya sea a través de la coincidencia de líneas múltiples o el uso de condiciones, con suerte es útil para algunas personas que buscan aquí. Así es como se verían los comandos del ejemplo:

Multilínea: sift -lm 'abc.*efg' testfile
Condiciones: sift -l 'abc' testfile --followed-by 'efg'

También puede especificar que ‘efg’ tenga que seguir ‘abc’ dentro de una cierta cantidad de líneas:
sift -l 'abc' testfile --followed-within 5:'efg'

Puede encontrar más información sobre sift-tool.org .

Tristemente, no puedes. De los documentos grep :

grep busca en los ARCHIVOS de entrada nombrados (o en la entrada estándar si no se nombran los archivos, o si se da un solo guión-menos (-) como nombre de archivo) para las líneas que contienen una coincidencia con el PATRÓN dado.

Mientras que la opción sed es la más simple y fácil, el one-liner de LJ lamentablemente no es el más portátil. Quienes se queden con una versión del C Shell necesitarán escapar de sus explosiones:

 sed -e '/abc/,/efg/\!d' [file] 

Desafortunadamente, esto no funciona en bash et al.

Si está dispuesto a usar contextos, esto podría lograrse escribiendo

 grep -A 500 abc test.txt | grep -B 500 efg 

Esto mostrará todo entre “abc” y “efg”, siempre que estén dentro de las 500 líneas entre sí.

Si necesita que ambas palabras se cierren entre sí, por ejemplo, no más de 3 líneas, puede hacer esto:

 find . -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg" 

El mismo ejemplo, pero solo filtra archivos * .txt:

 find . -name *.txt -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg" 

Y también puede reemplazar el comando grep con el comando egrep si también lo desea con expresiones regulares.

 #!/bin/bash shopt -s nullglob for file in * do r=$(awk '/abc/{f=1}/efg/{g=1;exit}END{print g&&f ?1:0}' file) if [ "$r" -eq 1 ];then echo "Found pattern in $file" else echo "not found" fi done 

puede usar grep en caso de que no esté interesado en la secuencia del patrón.

 grep -l "pattern1" filepattern*.* | xargs grep "pattern2" 

ejemplo

 grep -l "vector" *.cpp | xargs grep "map" 

grep -l encontrará todos los archivos que coinciden con el primer patrón, y xargs grep para el segundo patrón. Espero que esto ayude.

Con buscador de plata :

 ag 'abc.*(\n|.)*efg' 

similar a la respuesta del portador del anillo, pero con ag en su lugar. Las ventajas de velocidad del buscador de plata podrían brillar aquí.

Como alternativa a la respuesta de Balu Mohan, es posible imponer el orden de los patrones utilizando solo grep , head y tail :

 for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep "pattern2" &>/dev/null && echo $f; done 

Aunque esta no es muy bonita. Formateado más legible:

 for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null \ | grep -q "pattern2" \ && echo $f done 

Esto imprimirá los nombres de todos los archivos donde aparece "pattern2" después de "pattern1" , o donde ambos aparecen en la misma línea :

 $ echo "abc def" > a.txt $ echo "def abc" > b.txt $ echo "abcdef" > c.txt; echo "defabc" > d.txt $ for f in *.txt; do tail $f -n +$(grep -n "abc" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep -q "def" && echo $f; done a.txt c.txt d.txt 

Explicación

  • tail -n +i – imprime todas las líneas después del i ésimo, inclusive
  • grep -n – anteponer líneas coincidentes con sus números de línea
  • head -n1 – imprime solo la primera fila
  • cut -d : -f 1 – imprime la primera columna de corte usando : como el delimitador
  • 2>/dev/null – salida de error de silencio de tail que ocurre si la expresión $() regresa vacía
  • grep -q – silent grep y devuelve inmediatamente si se encuentra una coincidencia, ya que solo estamos interesados ​​en el código de salida

Esto debería funcionar también ?!

 perl -lpne 'print $ARGV if /abc.*?efg/s' file_list 

$ARGV contiene el nombre del archivo actual al leer las búsquedas del modificador de la file_list /s de file_list /s en la línea nueva.

El patrón de archivo *.sh es importante para evitar que los directorios sean inspeccionados. Por supuesto, algunas pruebas podrían evitar eso también.

 for f in *.sh do a=$( grep -n -m1 abc $f ) test -n "${a}" && z=$( grep -n efg $f | tail -n 1) || continue (( ((${z/:*/}-${a/:*/})) > 0 )) && echo $f done 

los

 grep -n -m1 abc $f 

busca el máximo de 1 coincidencia y devuelve (-n) el número de canvas. Si se encontró una coincidencia (test -n …) encuentra el último partido de efg (encuentra todo y toma el último con tail -n 1).

 z=$( grep -n efg $f | tail -n 1) 

else continuar

Dado que el resultado es algo así como 18:foofile.sh String alf="abc"; necesitamos cortar de “:” hasta el final de la línea.

 ((${z/:*/}-${a/:*/})) 

Debería devolver un resultado positivo si el último partido de la segunda expresión ha pasado la primera coincidencia de la primera.

Luego reportamos el nombre de archivo echo $f .

Esto debería funcionar:

 cat FILE | egrep 'abc|efg' 

Si hay más de una coincidencia, puede filtrar usando grep -v