grep para encontrar instancias de "Foo" donde "Bar" no aparece dentro de 10 líneas

10

Supongamos que quiero buscar en un árbol completo todos los archivos CPP donde aparece "Foo". Yo podría hacer:

find . -name "*.cpp" | xargs grep "Foo"

Ahora suponga que quiero enumerar solo aquellas instancias en las que alguna otra cadena, digamos "Bar", no aparece dentro de las 3 líneas del resultado anterior.

Entonces dados dos archivos:

a.cpp

1 Foo
2 qwerty
3 qwerty

b.cpp

1 Foo
2 Bar
3 qwerty

Me gustaría construir una búsqueda simple donde se encuentre "Foo" de a.cpp, pero "Foo" de b.cpp no.

¿Hay alguna manera de lograr esto de una manera bastante simple?

John Dibling
fuente
Tal vez la solución podría estar en la opción grep -A y / o grep -B y / o grep -C. Estoy intentando pero sin éxito ...
maurelio79
@ maurelio79: Mi teoría actual es esta. Grep para "Foo" usando -A 10 para el contexto. Conduzca eso a grep -v Bar. Canalice eso para obtener el nombre de archivo y el número de línea. Canalice eso a (¿algo?) Para imprimir esa línea.
John Dibling

Respuestas:

17

Con pcregrep:

pcregrep --include='\.cpp$' -rnM 'Foo(?!(?:.*\n){0,2}.*Bar)' .

La clave está en la -Mopción que es única pcregrepy se usa para unir varias líneas ( pcregrepextrae más datos del archivo de entrada según sea necesario cuando se camina, el RE lo exige).

(?!...)es el operador RE de anticipación negativa perl / PCRE. Foo(?!...)coincide Foosiempre ...que no coincida con lo que sigue.

...siendo (?:.*\n){0,2}.*Bar( .que no coincida con un carácter de nueva línea), que es de 0 a 2 líneas seguidas por una línea que contiene Bar.

Stéphane Chazelas
fuente
+1: excelente. Muchas gracias; Estoy seguro de que no fue fácil descubrir la expresión regular correcta. Aprecio mucho tus esfuerzos. Esto parece estar funcionando exactamente como quería.
John Dibling
2
Pregunta secundaria si te gustaría responder. ¿Cómo llegaste a saber pcregrep? Nunca he oído hablar de eso antes.
John Dibling
@JohnDibling, Yo he encontrado recientemente en unix.SE . Ese RE no es particularmente complejo, especialmente cuando está familiarizado con el operador RE (?!...)negativo de anticipación perl.
Stéphane Chazelas
9

No importa, solo utilícelo pcregrepcomo lo sugiere @StephaneChazelas.


Esto debería funcionar:

$ find . -name "*.cpp" | 
    while IFS= read -r file; do 
      grep -A 3 Foo "$file" | grep -q Bar || echo "$file"; 
    done 

La idea es utilizar el -Ainterruptor de grep para generar las líneas coincidentes y las N líneas siguientes. Luego pasa el resultado a través de ay grep Barsi eso no coincide (salir> 0), entonces repite el nombre del archivo.

Si sabe que tiene nombres de archivo sanos (sin espacios, líneas nuevas u otros caracteres extraños), puede simplificarlo para:

$ for file in $(find . -name "*.cpp"); do 
   grep -A 3 Foo "$file" | grep -q Bar || echo "$file"; 
  done 

Por ejemplo:

terdon@oregano foo $ cat a.cpp 
1 Foo
2 qwerty
3 qwerty
terdon@oregano foo $ cat b.cpp 
1 Foo
2 Bar
3 qwerty
terdon@oregano foo $ cat c.cpp 
1 Foo
2 qwerty
3 qwerty
4 qwerty
5. Bar
terdon@oregano foo $ for file in $(find . -name "*.cpp"); do grep -A 3 Foo "$file" | grep -q Bar || echo "$file"; done 
./c.cpp
./a.cpp

Tenga en cuenta que c.cppse devuelve a pesar de contener Barporque la línea con Bares más de 3 líneas después Foo. Puede controlar el número de líneas que desea buscar cambiando el valor pasado a -A:

$ for file in $(find . -name "*.cpp"); do 
   grep -A 10 Foo "$file" | grep -q Bar || echo "$file"; 
  done 
./a.cpp

Aquí hay uno más corto (suponiendo que lo use bash):

$ shopt -s globstar 
$ for file in **/*cpp; do 
    grep -A 10 Foo "$file" | grep -q Bar || echo "$file"; 
  done

IMPORTANTE

Como Stephane Chazelas señaló en los comentarios, las soluciones anteriores también imprimirán archivos que no contienen Foonada. Este evita que:

for file in **/*cpp; do 
  grep -qm 1 Foo "$file" && 
  (grep -A 3 Foo "$file" | grep -q Bar || echo "$file"); 
done
terdon
fuente
+1 ordenado. Poco más complejo de lo que esperaba, pero no está nada mal.
John Dibling
Eso supone que "Foo" ocurre solo una vez. Eso también informará los archivos que no contienen Foo. Te faltan citas.
Stéphane Chazelas
@StephaneChazelas gracias, citas fijas. Tienes toda la razón sobre informar archivos con no Fooy lo arreglé, pero no entiendo tu punto sobre múltiples instancias de Foo. Debe tratar con ellos correctamente.
terdon
@JohnDibling ver actualizaciones.
terdon
1
No informaría un archivo que contiene 100 líneas de "Foo" seguido de "Bar".
Stéphane Chazelas
0

No probado, estoy en mi teléfono:

find . -name "*.cpp" | xargs awk '/foo/{t=$0;c=10}/bar/{c=0;t=""}c{c--}t&&!c{print t;t=""}END&&t{print t}' 

algo como eso.

w00t
fuente