pliki grep z listy

14

Próbuję uruchomić grep na liście kilkuset plików:

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

Jednak pomimo tego, że szukam ciągu, o którym wiem, że znajduje się w plikach, następujące pliki nie wyszukują plików:

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

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

Znam -fflagę, która odczytuje wzorce z pliku. Ale jak czytać pliki wejściowe ?

Rozważyłem straszne obejście kopiowania plików do katalogu tymczasowego, ponieważ cpwydaje się, że obsługuje ten <(cat files.txt)format, i stamtąd grepowanie plików. Shirley jest lepszy sposób.

dotancohen
źródło

Odpowiedzi:

22

Wygląda na to, że przeglądasz listę nazw plików, a nie same pliki. <(cat files.txt)po prostu wyświetla listę plików. Spróbuj <(cat $(cat files.txt))połączyć je i przeszukaj je jako pojedynczy strumień lub

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

aby dać grepowi wszystkie pliki.

Jeśli jednak na liście znajduje się zbyt wiele plików, możesz mieć problemy z liczbą argumentów. W takim razie po prostu napiszę

while read filename; do grep -Hi 'foo' "$filename"; done < files.txt
orion
źródło
Dziękuję Ci! Nie zdawałem sobie sprawy, że whilemożna otrzymać linie file.txt jako takie.
dotancohen
Będziesz chciał tutaj wyłączyć globalną część operatora split + glob (chyba że powłoka to zsh).
Stéphane Chazelas,
1
whilenie odbiera dokładnie wierszy z pliku, readrobi to; whilepozwala nam to zrobić tylko w pętli. Pętla kończy się, gdy kończy się readniepowodzeniem (tzn. Zwraca niezerowy kod powrotu), zwykle z powodu osiągnięcia końca pliku.
PM 2,
1
Aby przeczytać wiersz (tekst), składnia jest IFS= read -r filename, read filenameto coś innego.
Stéphane Chazelas,
1
Zauważ, że -Hjest to rozszerzenie GNU. Tracisz trochę --.
Stéphane Chazelas,
8
xargs grep -i -- foo /dev/null < files.txt

zakładając, że pliki są puste lub rozdzielane znakami nowej linii (gdzie można użyć cudzysłowów lub ukośników odwrotnych do tych separatorów). W GNU xargsmożesz określić ogranicznik za pomocą -d(który następnie wyłącza obsługę cytowania).

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

zakładając, że pliki są oddzielone spacją, tabulatorem lub znakiem nowej linii (nie ma możliwości ucieczki przed nimi, chociaż można wybrać inny separator, przypisując go IFS). Ten nie powiedzie się, jeśli lista plików jest zbyt duża w większości systemów.

Zakładają również, że żaden plik nie jest wywoływany -.

Stéphane Chazelas
źródło
Lepiej / szybciej jest używać $(< file)zamiast $(cat file), przynajmniej w bashi zsh.
jimmij
7

Aby odczytać listę nazw plików ze standardowego wejścia, możesz użyć xargs. Na przykład,

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

Domyślnie xargsodczytuje elementy ze standardowego wejścia, rozdzielane spacjami. -d'\n'Informuje go używać nowej linii jako separatora argumentów, więc może obsługiwać nazwy plików zawierające spacje. (Jak zauważa Stéphane Chazelas, jest to rozszerzenie GNU). Jednak nie poradzi sobie z nazwami plików zawierającymi znaki nowej linii; potrzebowalibyśmy nieco bardziej skomplikowanego podejścia, aby sobie z nimi poradzić.

FWIW, takie podejście jest nieco szybsze niż while readpętla, ponieważ readpolecenie bash jest bardzo wolne - odczytuje dane znak po znaku, podczas gdy xargsbardziej efektywnie odczytuje dane wejściowe. Ponadto xargswywołuje greppolecenie tylko tyle razy, ile potrzeba, a każde wywołanie otrzymuje wiele nazw plików, a to jest bardziej wydajne niż wywoływanie greppojedynczo dla każdej nazwy pliku.

Zobacz stronę manuala xargs i stronę informacyjną xargs, aby uzyskać dodatkowe informacje.

PM 2 Ring
źródło
3

xargsmoże czytać elementy z pliku (np. z files.txtlisty) za pomocą tej opcji:

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

To też powinno działać:

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

lub dla spacji w nazwach plików

xargs -d'\n' -a files.txt grep -i 'foo'
xargs -I{} -a files.txt grep -i 'foo' {}
Xen2050
źródło
1

Możesz także zrobić za, ale przykład Oriona jest najprostszy:

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

(Dla każdego pliku wymienionego w pliku.txt wykonaj na nim polecenie grep.)

Michael
źródło