fichiers grep de la liste

14

J'essaie d'exécuter grep sur une liste de quelques centaines de fichiers:

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

Cependant, même si je recherche une chaîne que je connais dans les fichiers, ce qui suit ne recherche pas les fichiers:

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

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

Je connais le -fdrapeau qui lira les motifs d'un fichier. Mais comment lire les fichiers d'entrée ?

J'avais envisagé l'horrible solution de contournement de la copie des fichiers dans un répertoire temporaire qui cpsemble prendre en charge le <(cat files.txt)format, et à partir de là, en saluant les fichiers. Shirley, il y a une meilleure façon.

dotancohen
la source

Réponses:

22

Vous semblez saluer la liste des noms de fichiers, pas les fichiers eux-mêmes. <(cat files.txt)répertorie simplement les fichiers. Essayez <(cat $(cat files.txt))de les concaténer et de les rechercher en tant que flux unique, ou

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

pour donner à grep tous les fichiers.

Cependant, s'il y a trop de fichiers dans la liste, vous pouvez avoir des problèmes avec le nombre d'arguments. Dans ce cas, j'écrirais

while read filename; do grep -Hi 'foo' "$filename"; done < files.txt
orion
la source
Je vous remercie! Je ne savais pas que whilepouvait recevoir les lignes de file.txt en tant que telles.
dotancohen
Vous voudrez désactiver la partie glob de cet opérateur split + glob ici (sauf si le shell est zsh).
Stéphane Chazelas
1
whilene reçoit pas exactement les lignes du fichier, le readfait; whilenous permet simplement de faire cela en boucle. La boucle se termine en cas d' readéchec (c'est-à-dire renvoie un code retour différent de zéro), normalement en raison de la fin du fichier atteinte.
PM 2Ring
1
Pour lire une ligne (texte), la syntaxe est IFS= read -r filename, read filenamec'est autre chose.
Stéphane Chazelas
1
Notez qu'il -Hs'agit d'une extension GNU. Vous en manquez --.
Stéphane Chazelas
8
xargs grep -i -- foo /dev/null < files.txt

en supposant que les fichiers sont vides ou délimités par des sauts de ligne (où les guillemets ou les barres obliques inverses peuvent être utilisés pour échapper à ces séparateurs). Avec GNU, xargsvous pouvez spécifier le délimiteur avec -d(ce qui désactive alors la gestion des citations).

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

en supposant que les fichiers sont séparés par des espaces, des tabulations ou des sauts de ligne (aucun moyen de les échapper bien que vous puissiez choisir un séparateur différent en l'affectant à IFS). Celui-ci échouera si la liste des fichiers est trop grande sur la plupart des systèmes.

Ceux-ci supposent également qu'aucun des fichiers n'est appelé -.

Stéphane Chazelas
la source
Il est préférable / plus rapide d'utiliser $(< file)au lieu de $(cat file), au moins dans bashet zsh.
jimmij
7

Pour lire une liste de noms de fichiers depuis stdin, vous pouvez utiliser xargs. Par exemple,

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

Par défaut, xargslit les éléments de l'entrée standard, délimités par des blancs. Le lui -d'\n'indique d'utiliser la nouvelle ligne comme délimiteur d'argument, afin de pouvoir gérer les noms de fichiers contenant des espaces. (Comme le souligne Stéphane Chazelas, c'est une extension GNU). Cependant, il ne supportera pas les noms de fichiers contenant des retours à la ligne; nous aurions besoin d'une approche un peu plus compliquée pour les gérer.

FWIW, cette approche est un peu plus rapide qu'une while readboucle, car la readcommande de bash est très lente - elle lit ses données caractère par caractère, tandis qu'elle xargslit son entrée plus efficacement. En outre, xargsn'appelle la grepcommande autant de fois que nécessaire, chaque appel recevant plusieurs noms de fichier, et c'est plus efficace que d'appeler grepindividuellement pour chaque nom de fichier.

Consultez la page de manuel de xargs et la page d'informations de xargs pour plus de détails.

PM 2Ring
la source
3

xargspeut lire des éléments d'un fichier (comme votre files.txtliste) avec son option:

   --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.

Cela devrait donc aussi fonctionner:

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

ou pour les espaces dans les noms de fichiers

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

Vous pouvez également faire un for mais l'exemple d'Orion est le plus simple:

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

(Pour chaque fichier répertorié dans le fichier files.txt, exécutez la commande grep dessus.)

Michael
la source