Co robi „xargs grep”?

25

Znam greppolecenie i uczę się o jego funkcjach xargs, więc przeczytałem stronę, która zawiera kilka przykładów użycia xargspolecenia.

Mylę mnie ostatni przykład, przykład 10. Mówi on: „Polecenie xargs wykonuje polecenie grep, aby znaleźć wszystkie pliki (spośród plików udostępnionych przez polecenie find), które zawierały ciąg„ stdlib.h ””

$ find . -name '*.c' | xargs grep 'stdlib.h'
./tgsthreads.c:#include
./valgrind.c:#include
./direntry.c:#include
./xvirus.c:#include
./temp.c:#include
...
...
...

Jaka jest jednak różnica w zwykłym użyciu

$ find . -name '*.c' | grep 'stdlib.h'

?

Oczywiście wciąż mam problemy z tym, co dokładnie robi xargs, więc doceniam każdą pomoc!

AlphaOmega
źródło
2
To pytanie może być pomocne: Kiedy potrzebny jest XArgs?
TheOdd

Odpowiedzi:

30
$ find . -name '*.c' | grep 'stdlib.h'

To potoczy wyjście (stdout) * od finddo (stdin of) * grep 'stdlib.h' jako tekst (tzn. Nazwy plików są traktowane jak tekst). greprobi to, co zwykle i znajduje pasujące linie w tym tekście (dowolne nazwy plików, które same zawierają wzorzec). Zawartość plików nigdy nie jest odczytywana.

$ find . -name '*.c' | xargs grep 'stdlib.h'

To konstruuje polecenie, grep 'stdlib.h' dla którego każdy wynik findjest argumentem - będzie więc szukał dopasowań w każdym znalezionym pliku find( xargsmożna go traktować jako przekształcenie standardowego wejścia w argumenty dla podanych poleceń) *

Użyj -type fw poleceniu find, w przeciwnym razie pojawią się błędy związane z grepdopasowaniem katalogów. Ponadto, jeśli nazwy plików mają spacje, xargsźle się zepsują, więc użyj separatora zerowego, dodając -print0i xargs -0dla bardziej wiarygodnych wyników:

find . -type f -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

* dodał te dodatkowe wyjaśnienia, jak sugeruje w komentarzu @cat

Zanna
źródło
2
można rozważyć zauważyć (ponieważ wydaje się, pominięte) Kluczowym punktem, że |rury stdout do grep stdin , która nie jest taka sama, jak argumenty grep i daje mylące wyniki.
kot
1
Lub użyj GNU find find -name '*.c' -exec grep stdlib.h {} +. Prawie nigdy nie używam xargs. Zaskoczony również nikt nie wspomniał, że xargs służy podobnemu celowi do grep $(find)zastępowania poleceń, więc napisałem własną odpowiedź. Wyjaśnienie xargs jako podstawienia poleceń z mniejszymi ograniczeniami i problemami wydaje się naturalne.
Peter Cordes
Jedną z sytuacji, w której używam xargs, jest usunięcie wielu plików w wyniku wyszukiwania. Jeśli po prostu użyjesz -exec rm, uruchomi on rm na każdym pliku pojedynczo, co jest bardzo nieefektywne. Piping do xargs wykona je wszystkie naraz za pomocą jednego rm. Ograniczenie za pomocą powiedzmy -n50 (do 50 na raz) może zapobiec przepełnieniu wiersza poleceń (problem z dużą ilością plików).
lsd
1
@lsd: Dlaczego nie find -deletew tym specjalnym przypadku? Lub w przypadku poleceń innych niż rm, jeśli masz GNU find, -exec some_command {} +grupuj je w pakiety takie jak xargs, zamiast tego \;zachowanie uruchamiania polecenia osobno dla każdego.
Peter Cordes
@lsd finduruchamia polecenie dla każdego pliku, tylko wtedy, gdy używa -exec command \;Oba, xargsi -exec command \+wywoła polecenie z maksymalną liczbą argumentów dozwolonych przez system. Innymi słowy, są one równoważne
Sergiy Kolodyazhnyy
6

xargs pobiera standardowe dane wejściowe i zamienia je w argumenty wiersza poleceń.

find . -name '*.c' | xargs grep 'stdlib.h' jest bardzo podobny do

grep 'stdlib.h' $(find . -name '*.c')  # UNSAFE, DON'T USE

I da takie same wyniki, o ile lista nazw plików nie będzie zbyt długa dla pojedynczego wiersza poleceń. (Linux obsługuje megabajty tekstu w jednym wierszu poleceń, więc zwykle nie potrzebujesz xargs).


Ale oba są do bani, ponieważ pękają, jeśli twoje nazwy plików zawierają spacje . Zamiast tego find -print0 | xargs -0działa, ale tak też jest

find . -name '*.c' -exec grep 'stdlib.h' {} +

To nigdy nie potokuje nazw plików: umieszcza findje w dużej linii poleceń i uruchamia grepbezpośrednio.

\;zamiast +uruchamia grep osobno dla każdego pliku, co jest znacznie wolniejsze. Nie rób tego Ale +jest rozszerzeniem GNU, więc musisz xargsto zrobić skutecznie, jeśli nie możesz założyć GNU find.


Jeśli pominiesz xargs, find | grepczy jego wzorzec pasuje do listy nazw plików, które są finddrukowane.

Więc w tym momencie równie dobrze możesz to zrobić find -name stdlib.h. Oczywiście, z -name '*.c' -name stdlib.h, nie dostaniesz żadnego wyniku, ponieważ te wzorce nie mogą się zgadzać, a domyślne zachowanie find jest zgodne z regułami AND.

Zastąp lessw dowolnym momencie procesu, aby zobaczyć, jaki wynik generuje jakakolwiek część rurociągu.


Dalsza lektura: http://mywiki.wooledge.org/BashFAQ ma kilka świetnych rzeczy.

Peter Cordes
źródło
1
GNU xargs musi również -dustawić separator, abyś mógł -d'\n'obsługiwać listę rozdzielaną znakiem nowej linii, co może być przydatne, jeśli obsługujesz listę nazw plików w pliku itp. (O ile nazwy plików nie mają nowe linie w nich, to znaczy.)
ilkkachu
@ilkkachu: tak, nowe wiersze w nazwach plików są znacznie rzadsze niż spacje, ponieważ łamią większość skryptów. myfunc(){ local IFS=$'\n'; fgrep stdlib.h` $ (find) ; }działa również z tym samym efektem. Lub jako (IFS=...; cmd...)jednowierszowa podpowłoka również działa, aby zawrzeć zmianę w IFS bez konieczności jej zapisywania / przywracania.
Peter Cordes
@PeterCordes Nie rób command $( find )takich rzeczy. Problematyczne nazwy plików ze spacjami i znakami specjalnymi mogą uszkodzić tego typu rzeczy. Przynajmniej podwójnie cytuj zastąpienie polecenia.
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy: Dzięki za zwrócenie uwagi, że wygląda na to, że tak naprawdę polecam to zrobić. Ludzie przeglądający mogliby to skopiować / wkleić zamiast czytać następną sekcję. Zaktualizowano, aby rozwiązać ten problem.
Peter Cordes
@SergiyKolodyazhnyy: A może odpowiadałeś na mój komentarz? Zauważ, że ustawiłem, IFSwięc jest to równoważne z używaniem xargs '-d\n'. Ekspansja globu i przetwarzanie metaznaków powłoki ma miejsce przed efektami podstawienia polecenia, więc myślę, że jest bezpieczny nawet w przypadku nazw plików zawierających $()lub >. Zgodzono się, że dzielenie słów przy zastępowaniu poleceń nie jest dobrą praktyką, z wyjątkiem jednorazowego interaktywnego użycia, w którym wiesz coś o nazwach plików. command "$(find)"Przydaje się jednak tylko wtedy, gdy oczekuje się, że wygeneruje dokładnie 1 nazwę pliku ...
Peter Cordes
5

Zasadniczo xargsjest używany w przypadkach, w których należy potokować (za pomocą symbolu |) coś z jednego polecenia do drugiego ( Command1 | Command2), ale dane wyjściowe z pierwszego polecenia nie są poprawnie odbierane jako dane wejściowe dla drugiego polecenia.

Zwykle dzieje się tak, gdy drugie polecenie nie obsługuje poprawnie wprowadzania danych przez Standard In (standardowe wejście) (np .: Wiele wierszy jako danych wejściowych, sposób konfiguracji linii, znaki używane jako dane wejściowe, wiele parametrów jako dane wejściowe, typ danych odbierany jako wejście itp.). Aby dać ci szybki przykład, przetestuj następujące elementy:

Przykład 1:

ls | echo- To nic nie da, ponieważ echonie wie, jak obsłużyć otrzymywane dane wejściowe. Teraz w tym przypadku, jeśli korzystamyxargs go , przetworzy dane wejściowe w sposób, który może być poprawnie obsługiwany przez echo(np .: jako pojedynczy wiersz informacji)

ls | xargs echo - Spowoduje to wyświetlenie wszystkich informacji z ls jednego wiersza

Przykład 2:

Załóżmy, że mam wiele plików goLang w folderze o nazwie go. Szukałbym ich z czymś takim:

find go -name *.go -type f | echo - Ale jeśli symbol rury tam i echo na końcu, nie zadziała.

find go -name *.go -type f | xargs echo- Tutaj zadziałałoby dzięki, xargsale gdybym chciał każdej odpowiedzi zfind polecenia w jednym wierszu, zrobiłbym następujące:

find go -name *.go -type f | xargs -0 echo - W tym przypadku ta sama moc wyjściowa find byłaby pokazana przez echo.

Polecenia takie jak cp, echo, rm, lessi inne, które wymagają lepszego sposobu obsługi danych wejściowych, zyskują, gdy są używane z xargs.

Luis Alvarado
źródło
4

xargs służy do automatycznego generowania argumentów wiersza poleceń opartych (zwykle) na liście plików.

Rozważając kilka alternatyw dla używania xargspolecenia następującego:

find . -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

Istnieje kilka powodów, aby używać go zamiast innych opcji, które nie zostały pierwotnie wymienione w innych odpowiedziach:

  1. find . -name '*.c' -exec grep 'stdlib.h' {}\;wygeneruje jeden grepproces dla każdego pliku - jest to ogólnie uważane za złą praktykę i może powodować duże obciążenie systemu, jeśli znaleziono wiele plików.
  2. Jeśli jest dużo plików, grep 'stdlib.h' $(find . -name '*.c')polecenie prawdopodobnie się nie powiedzie, ponieważ wynik $(...)operacji przekroczy maksymalną długość wiersza poleceń powłoki

Jak wspomniano w innych odpowiedziach, powodem użycia -print0argumentu do findw tym scenariuszu i -0argumentu do xargs jest to, że nazwy plików z niektórymi znakami (np. Cudzysłowy, spacje, a nawet znaki nowej linii) są nadal obsługiwane poprawnie.

Michael Firth
źródło