¿Cómo buscar archivos donde existen dos palabras diferentes?

14

Estoy buscando una manera de buscar archivos donde existen instancias de dos palabras en el mismo archivo. He estado usando lo siguiente para realizar mis búsquedas hasta este punto:

find . -exec grep -l "FIND ME" {} \;

El problema con el que me encuentro es que si no hay exactamente un espacio entre "ENCONTRAR" y "ME", el resultado de la búsqueda no produce el archivo. ¿Cómo adapto la cadena de búsqueda anterior donde ambas palabras "ENCONTRAR" y "ME existen en un archivo en lugar de" ENCONTRARME "?

Estoy usando AIX.

Chad Harrison
fuente
1
¿Las palabras existen en algún lugar del archivo o siempre están en la misma línea?
Sobrique
La intención era la misma línea.
Chad Harrison
Una alternativa, si las palabras están en la misma línea, es usar una expresión regular con grep -E/ egrepque describa todos los patrones que le interesan (y usar en +lugar de ;si su hallazgo tiene soporte para +.)
MattBianco

Respuestas:

21

Con herramientas GNU:

find . -type f  -exec grep -lZ FIND {} + | xargs -r0 grep -l ME

Puedes hacer de manera estándar:

find . -type f -exec grep -q FIND {} \; -exec grep -l ME {} \;

Pero eso ejecutaría dos greps por archivo. Para evitar ejecutar tantos grepcorreos electrónicos y seguir siendo portátil mientras se permite cualquier carácter en los nombres de archivo, puede hacer lo siguiente:

convert_to_xargs() {
  sed "s/[[:blank:]\"\']/\\\\&/g" | awk '
    {
      if (NR > 1) {
        printf "%s", line
        if (!index($0, "//")) printf "\\"
        print ""
      }
      line = $0
    }'
    END { print line }'
}

find .//. -type f |
  convert_to_xargs |
  xargs grep -l FIND |
  convert_to_xargs |
  xargs grep -l ME

La idea es convertir la salida de finda un formato adecuado para xargs (que espera un espacio en blanco (SPC / TAB / NL, y los otros espacios en blanco de su configuración regional con algunas implementaciones de xargs) lista separada de palabras donde las comillas simples, dobles y barras invertidas pueden escapar espacios en blanco y entre sí).

En general, no puede procesar la salida de find -print, ya que separa los nombres de archivo con un carácter de nueva línea y no escapa a los caracteres de nueva línea que se encuentran en los nombres de archivo. Por ejemplo si vemos:

./a
./b

No tenemos forma de saber si se trata de un archivo llamado ben un directorio llamado a<NL>.o si son los dos archivos ay b.

Al usar .//., porque //no puede aparecer de otra manera en una ruta de archivo como resultado find(porque no existe un directorio con un nombre vacío y /no está permitido en un nombre de archivo), sabemos que si vemos una línea que contiene //, entonces eso es la primera línea de un nuevo nombre de archivo. Entonces podemos usar ese awkcomando para escapar de todos los caracteres de nueva línea, pero aquellos que preceden a esas líneas.

Si tomamos el ejemplo anterior, findsaldría en el primer caso (un archivo):

.//a
./b

Que awk escapa a:

.//a\
./b

Entonces eso lo xargsve como un argumento. Y en el segundo caso (dos archivos):

.//a
.//b

Lo awkque dejaría como está, así que xargsve dos argumentos.

Stéphane Chazelas
fuente
¿Por qué no usar find ... -print0y en su grep --nulllugar?
saqueó el
@razzed, no estoy seguro de qué quieres decir con eso. grep --null(también conocido como -Z) se usa en el primero pero es una extensión de GNU. -print0(otra extensión de GNU) no ayudaría aquí.
Stéphane Chazelas
Gracias. Me gustaría envolver su código de shell en un script que toma el directorio de búsqueda como argumento desde la línea de comandos. Todavía no estoy muy seguro de lo que .//.significa, y me pregunto cómo puedo modificar eso para aceptar un argumento desde la línea de comandos, por ejemplo $1.
Tim
Gracias. En su comando, ¿es necesario usar -print0con findy -0con xargs?
Tim
@Tim, no estoy seguro de lo que quieres decir. No uso find -print0en ninguna parte de mi respuesta.
Stéphane Chazelas
8

Si los archivos están en un solo directorio y su nombre no contienen espacios, tabuladores, nuevas líneas *, ?ni [caracteres y no se inician con -ni ., esto va a obtener una lista de los archivos que contienen ME, que luego reducir a los que También contienen FIND.

grep -l FIND `grep -l ME *`
usuario45529
fuente
¡ESTO necesita más votos a favor! Mucho más elegante que la respuesta "aceptada". Trabajó para mi.
roblogic
Solo lo hice grep -l CategoryLinearAxis `grep -l labelJsFunction *`mientras buscaba archivos que tenían ambos atributos. Qué manera perfecta de hacerlo. +1
WEBjuju
3

Con awkusted también podría ejecutar:

find . -type f  -exec awk 'BEGIN{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; END{if (cx > 0 && cy > 0) print FILENAME}' {} \;

Utiliza cxy cypara contar líneas coincidentes FINDy respectivamente ME. En el ENDbloque, si ambos contadores> 0, imprime el FILENAME.
Esto sería más rápido / más eficiente con gnu awk:

find . -type f  -exec gawk 'BEGINFILE{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; ENDFILE{if (cx > 0 && cy > 0) print FILENAME}' {} +
don_crissti
fuente
2

O use egrep -eo de grep -Eesta manera:

find . -type f -exec egrep -le '(ME.*FIND|FIND.*ME)' {} \;

o

find . -type f -exec grep -lE '(ME.*FIND|FIND.*ME)' {} +

El +hace encontrar (si es compatible) agregar múltiples nombres de archivo (ruta) como argumentos para el comando que se está -execeditando. Esto ahorra procesos y es mucho más rápido que lo \;que invoca el comando una vez por cada archivo encontrado.

-type f solo coincide con los archivos, para evitar grepping en un directorio.

'(ME.*FIND|FIND.*ME)'es una expresión regular que coincide con cualquier línea que contenga "ME" seguido de "FIND" o "FIND" seguido de "ME". (comillas simples para evitar que el shell interprete caracteres especiales).

Agregue -ia al grepcomando para que no distinga entre mayúsculas y minúsculas.

Para que solo coincidan las líneas donde "ENCONTRAR" aparece antes que "YO", use 'FIND.*ME'.

Para requerir espacios (1 o más, pero nada más) entre las palabras: 'FIND +ME'

Para permitir espacios (0 o más, pero nada más) entre las palabras: 'FIND *ME'

Las combinaciones son infinitas con expresiones regulares, y siempre que esté interesado en hacer coincidir solo una fila por vez, egrep es muy poderoso.

MattBianco
fuente
¿La mayoría de greps no admiten "-r"? Eso eliminaría el "hallazgo", pero podría haber sockets u otros archivos no planos en el árbol que se está buscando.
Stolenmoment
OP utiliza AIX y tenía finden la pregunta.
MattBianco
0

Mirando la respuesta aceptada, parece más complejo de lo que debe ser. Versiones GNU de findy grepy xargsseries terminadas en nulo apoyo. Es tan simple como:

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l ME

Puede modificar su findcomando para filtrar a los archivos que desee, y funciona con nombres de archivo que contienen cualquier carácter; sin la complejidad añadida de sedanálisis. Si desea procesar más los archivos, agregue otro --nullal últimogrep

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l --null ME | xargs -0 echo

Y, como una función:

find_strings() {
    find . -type f -print0 | xargs -0 grep -l --null "$1" | xargs -0 grep -l "$2"
}

Obviamente, use la respuesta aceptada si no está ejecutando versiones GNU de estas herramientas.

desgarrado
fuente
1
--null, --print0, -0Son todas las extensiones de GNU. Aunque algunos de ellos se encuentran en otras implementaciones hoy en día, todavía no son portátiles y no están en el estándar POSIX o Unix.
Stéphane Chazelas