grep archivos de la lista

14

Estoy tratando de ejecutar grep contra una lista de unos cientos de archivos:

$ head -n 3 <(cat files.txt)
admin.php
ajax/accept.php
ajax/add_note.php

Sin embargo, aunque estoy buscando una cadena que sé que se encuentra en los archivos, lo siguiente no busca los archivos:

$ grep -i 'foo' <(cat files.txt)

$ grep -i 'foo' admin.php
The foo was found

Estoy familiarizado con la -fbandera que leerá los patrones de un archivo. Pero, ¿cómo leer los archivos de entrada ?

Había considerado la horrible solución de copiar los archivos a un directorio temporal, ya que cpparece admitir el <(cat files.txt)formato, y a partir de ahí agrupar los archivos. Shirley hay una mejor manera.

dotancohen
fuente

Respuestas:

22

Parece que estás haciendo grep en la lista de nombres de archivos, no en los archivos en sí. <(cat files.txt)solo enumera los archivos. Intenta <(cat $(cat files.txt))concatenarlos y buscarlos como una sola secuencia, o

grep -i 'foo' $(cat files.txt)

para dar grep todos los archivos.

Sin embargo, si hay demasiados archivos en la lista, puede tener problemas con la cantidad de argumentos. En ese caso solo escribiría

while read filename; do grep -Hi 'foo' "$filename"; done < files.txt
Orión
fuente
¡Gracias! No me di cuenta de que whilepodría recibir las líneas de file.txt como tal.
dotancohen
Querrá deshabilitar la parte glob de ese operador split + glob aquí (a menos que el shell sea zsh).
Stéphane Chazelas
1
whileno está recibiendo exactamente las líneas del archivo, readestá haciendo eso; whilesolo nos deja hacer eso en un bucle. El ciclo finaliza cuando readfalla (es decir, devuelve un código de retorno distinto de cero), normalmente debido a que se alcanza el Fin del archivo.
PM 2Ring
1
Para leer una línea (texto), la sintaxis es IFS= read -r filename, read filenamees otra cosa.
Stéphane Chazelas
1
Tenga en cuenta que -Hes una extensión GNU. Te estás perdiendo un poco --.
Stéphane Chazelas
8
xargs grep -i -- foo /dev/null < files.txt

suponiendo que los archivos estén en blanco o delimitados por nueva línea (donde las comillas o barras invertidas pueden usarse para escapar de esos separadores). Con GNU xargspuede especificar el delimitador con -d(que luego deshabilita el manejo de las comillas).

(unset -v IFS; set -f; grep -i -- foo $(cat files.txt))

suponiendo que los archivos estén separados por espacio, tabulación o nueva línea (no hay forma de escapar de ellos, aunque puede elegir un separador diferente asignándolo a IFS). Ese fallará si la lista de archivos es demasiado grande en la mayoría de los sistemas.

También se supone que ninguno de los archivos se llama -.

Stéphane Chazelas
fuente
Es mejor / más rápido de usar en $(< file)lugar de $(cat file), al menos en bashy zsh.
jimmij
7

Para leer una lista de nombres de archivo de stdin puede usar xargs. P.ej,

cat files.txt | xargs -d'\n' grep -i -- 'foo'

Por defecto, xargslee elementos de la entrada estándar, delimitados por espacios en blanco. El -d'\n'le dice que use nueva línea como delimitador de argumento, para que pueda manejar nombres de archivos que contienen espacios en blanco. (Como señala Stéphane Chazelas, esa es una extensión de GNU). Sin embargo, no hará frente a los nombres de archivo que contienen nuevas líneas; necesitaríamos un enfoque un poco más complicado para manejarlos.

FWIW, este enfoque es algo más rápido que un while readbucle, ya que el readcomando de bash es muy lento: lee sus datos carácter por carácter, mientras que xargslee su entrada de manera más eficiente. Además, xargssolo invoca el grepcomando tantas veces como sea necesario, con cada invocación recibiendo múltiples nombres de archivo, y eso es más eficiente que invocar grepindividualmente para cada nombre de archivo.

Consulte la página de manual de xargs y la página de información de xargs para obtener más detalles.

PM 2Ring
fuente
3

xargspuede leer elementos de un archivo (como su files.txtlista) con su opción:

   --arg-file=file
   -a file
          Read items from file instead of standard input.  If you use this
          option, stdin remains unchanged when commands are  run.   Other
          wise, stdin is redirected from /dev/null.

Entonces esto también debería funcionar:

xargs -a files.txt grep -i 'foo'

o para espacios en nombres de archivo

xargs -d'\n' -a files.txt grep -i 'foo'
xargs -I{} -a files.txt grep -i 'foo' {}
Xen2050
fuente
1

También puedes hacer un for pero el ejemplo de Orion es el más simple:

for i in $(cat files.txt); do grep -i 'foo' $i ; done

(Para cada archivo listado en files.txt, ejecute el comando grep en él).

Miguel
fuente