grep Dateien aus der Liste

14

Ich versuche, grep gegen eine Liste von ein paar hundert Dateien auszuführen:

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

Obwohl ich nach einer Zeichenfolge greife, von der ich weiß, dass sie in den Dateien enthalten ist, werden die Dateien nicht durchsucht:

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

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

Ich kenne das -fFlag, das die Muster aus einer Datei liest . Aber wie liest man die Eingabedateien ?

Ich hatte über die schreckliche Umgehung nachgedacht, die Dateien in ein temporäres Verzeichnis zu kopieren, das cpdas <(cat files.txt)Format zu unterstützen scheint , und von dort aus die Dateien zu durchsuchen. Shirley, es gibt einen besseren Weg.

dotancohen
quelle

Antworten:

22

Sie scheinen die Liste der Dateinamen zu überfliegen, nicht die Dateien selbst. <(cat files.txt)listet nur die Dateien. Versuchen Sie <(cat $(cat files.txt)), sie tatsächlich zu verketten und als einzelnen Stream zu suchen, oder

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

grep alle dateien geben.

Wenn sich jedoch zu viele Dateien in der Liste befinden, können Probleme mit der Anzahl der Argumente auftreten. In diesem Fall würde ich einfach schreiben

while read filename; do grep -Hi 'foo' "$filename"; done < files.txt
orion
quelle
Vielen Dank! Mir war nicht klar, dass whiledie Zeilen von file.txt als solche empfangen werden konnten.
Dotancohen
Sie möchten den Glob- Teil dieses split + glob- Operators hier deaktivieren (es sei denn, die Shell ist zsh).
Stéphane Chazelas
1
whileempfängt die Zeilen aus der Datei nicht genau, readtut das; whileLass uns das einfach in einer Schleife machen. Die Schleife endet, wenn ein readFehler auftritt (dh ein Rückkehrcode ungleich Null zurückgegeben wird), normalerweise weil das Dateiende erreicht ist.
PM 2Ring
1
Um eine (Text) Zeile gelesen, die Syntax ist IFS= read -r filename, read filenameist etwas anderes.
Stéphane Chazelas
1
Beachten Sie, dass dies -Heine GNU-Erweiterung ist. Sie vermissen einige --.
Stéphane Chazelas
8
xargs grep -i -- foo /dev/null < files.txt

Angenommen, Dateien sind leer oder durch Zeilenumbrüche getrennt (wobei Anführungszeichen oder Backslashes verwendet werden können, um diese Trennzeichen zu umgehen). Mit GNU können xargsSie das Trennzeichen angeben -d(wodurch die Behandlung von Zitaten jedoch deaktiviert wird).

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

Angenommen, Dateien sind durch Leerzeichen, Tabulatoren oder Zeilenumbrüche getrennt (dies ist nicht zu vermeiden, obwohl Sie ein anderes Trennzeichen auswählen können, indem Sie es zuweisen IFS). Dieser wird fehlschlagen, wenn die Dateiliste auf den meisten Systemen zu groß ist.

Diese gehen auch davon aus, dass keine der Dateien aufgerufen wird -.

Stéphane Chazelas
quelle
Es ist besser / schneller zu benutzen $(< file)als $(cat file), zumindest in bashund zsh.
Jimmy
7

Um eine Liste der Dateinamen von stdin zu lesen, können Sie verwenden xargs. Z.B,

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

xargsLiest standardmäßig Elemente aus der Standardeingabe, die durch Leerzeichen getrennt sind. Das -d'\n'weist es an, newline als Argumentbegrenzer zu verwenden, damit Dateinamen mit Leerzeichen verarbeitet werden können. (Wie Stéphane Chazelas betont, ist das eine GNU-Erweiterung). Es wird jedoch nicht mit Dateinamen mit Zeilenumbrüchen fertig; Wir brauchen einen etwas komplizierteren Ansatz, um damit umzugehen.

FWIW, dieser Ansatz ist etwas schneller als eine while readSchleife, da der bash- readBefehl sehr langsam ist - er liest seine Daten zeichenweise, während er xargsseine Eingabe effizienter liest. Außerdem xargsruft nur den grepBefehl so oft , wie es braucht, bei jedem Aufruf mehr Dateinamen empfängt, und das ist effizienter als Aufruf grepindividuell für jeden Dateinamen.

Weitere Informationen finden Sie in der xargs-Manpage und auf der xargs-Infoseite.

PM 2Ring
quelle
3

xargsfiles.txtMit der folgenden Option können Sie Elemente aus einer Datei (wie Ihrer Liste) lesen :

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

Das sollte also auch funktionieren:

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

oder für Leerzeichen in Dateinamen

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

Sie können auch ein für machen, aber Orions Beispiel ist das einfachste:

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

(Führen Sie für jede in der Datei files.txt aufgeführte Datei den Befehl grep aus.)

Michael
quelle