Was macht "xargs grep"?

25

Ich kenne den grepBefehl und lerne die Funktionen von kennen xargs. Ich lese diese Seite durch, die einige Beispiele für die Verwendung des xargsBefehls enthält.

Das letzte Beispiel, Beispiel 10, verwirrt mich. Es besagt: "Der Befehl xargs führt den Befehl grep aus, um alle Dateien (unter den vom Befehl find bereitgestellten Dateien) zu finden, die die Zeichenfolge 'stdlib.h' enthielten."

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

Was ist jedoch der Unterschied zur einfachen Verwendung

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

?

Offensichtlich kämpfe ich immer noch mit dem, was Xargs genau macht, also ist jede Hilfe dankbar!

Alpha Omega
quelle
2
Diese Frage kann hilfreich sein: Wann wird XArgs benötigt?
TheOdd

Antworten:

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

Dies leitet die Ausgabe (stdout) * von find(stdin von) * grep 'stdlib.h' als Text weiter (dh die Dateinamen werden als Text behandelt). grepmacht seine übliche Sache und findet die passenden Zeilen in diesem Text (alle Dateinamen, die selbst das Muster enthalten). Der Inhalt der Dateien wird niemals gelesen.

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

Diese erstellt einen Befehl grep 'stdlib.h' zu dem jedes Ergebnis findist ein Argument - so ist dies für die Spiele aussehen wird innerhalb jeder Datei gefunden find( xargskann seine stdin in Argumente an die gegebenen Befehle wie das Drehen gedacht werden) *

Verwenden Sie -type fin Ihrem Befehl find, oder es werden Fehler grepfür übereinstimmende Verzeichnisse angezeigt. Auch wenn die Dateinamen Leerzeichen enthalten, xargswird vermasseln schlecht, so verwenden Sie die Null - Separator durch Zugabe -print0und xargs -0für zuverlässigere Ergebnisse:

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

* fügte diese zusätzlichen erklärenden Punkte hinzu, wie im Kommentar von @cat vorgeschlagen

Zanna
quelle
2
Sie könnten unter Hinweis darauf , betrachten (weil es scheint , dass Sie weggelassen) der entscheidende Punkt , dass |Rohre stdout zu grep stdin , die nicht das gleiche wie grep Argumente und gibt verwirrende Ergebnisse.
Katze
1
Oder benutze GNU find's find -name '*.c' -exec grep stdlib.h {} +. Ich benutze so ziemlich nie Xargs. Ebenfalls überrascht erwähnte niemand, dass Xargs einem ähnlichen Zweck dient, grep $(find)um Substitution zu befehlen, also schrieb ich eine eigene Antwort. Xargs als Befehlsersetzung mit weniger Einschränkungen und Problemen zu erklären, erscheint natürlich.
Peter Cordes
Eine Situation, in der ich xargs verwende, ist, wenn ich aufgrund von find viele Dateien lösche. Wenn Sie nur -exec rm ausführen, wird rm für jede Datei einzeln ausgeführt, was sehr ineffizient ist. Das Piping zu xargs erledigt sie alle auf einmal mit einer rm. Das Begrenzen mit "say -n50" (jeweils "do 50") kann einen Befehlszeilenüberlauf verhindern (Problem mit vielen Dateien).
lsd
1
@ lsd: Warum nicht find -deletefür diesen speziellen Fall? Oder für andere Befehle als rm, wenn Sie GNU find haben, -exec some_command {} +gruppieren Sie sie in Gruppen wie xargs, stattdessen wird das \;Verhalten beim Ausführen des Befehls für jeden getrennt.
Peter Cordes
@lsd findführt den Befehl für jede Datei nur dann aus, wenn -exec command \;Both verwendet wird, xargsund -exec command \+ruft den Befehl mit der vom System maximal zulässigen Anzahl von Argumenten auf. Mit anderen Worten, sie sind gleichwertig
Sergiy Kolodyazhnyy
6

xargs nimmt seine Standardeingabe und wandelt sie in Befehlszeilenargumente um.

find . -name '*.c' | xargs grep 'stdlib.h' ist sehr ähnlich zu

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

Und liefert die gleichen Ergebnisse, solange die Liste der Dateinamen für eine einzelne Befehlszeile nicht zu lang ist. (Linux unterstützt Megabyte Text in einer einzigen Befehlszeile, daher benötigen Sie normalerweise keine xargs.)


Aber beide sind zum Kotzen, weil sie kaputt gehen, wenn Ihre Dateinamen Leerzeichen enthalten . Stattdessen find -print0 | xargs -0funktioniert, aber es funktioniert auch

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

Das leitet die Dateinamen niemals irgendwo weiter: findStapelt sie in eine große Befehlszeile und wird grepdirekt ausgeführt.

\;stattdessen wird +grep für jede Datei separat ausgeführt, was viel langsamer ist. Tu das nicht. Da +es sich jedoch um eine GNU-Erweiterung handelt, müssen xargsSie dies effizient tun, wenn Sie nicht davon ausgehen können, dass GNU dies findet.


Wenn Sie weglassen, xargswird find | grepdas Muster mit der Liste der zu druckenden Dateinamen findabgeglichen.

An diesem Punkt könnten Sie es genauso gut tun find -name stdlib.h. Natürlich erhalten -name '*.c' -name stdlib.hSie mit keine Ausgabe, da diese Muster nicht beide übereinstimmen können und Find standardmäßig UND die Regeln zusammen verwendet.

Ersetzen Sie lesszu jedem Zeitpunkt des Prozesses, um zu sehen, welche Ausgabe ein Teil der Pipeline erzeugt.


Lesen Sie weiter: http://mywiki.wooledge.org/BashFAQ hat einige großartige Sachen.

Peter Cordes
quelle
1
GNU xargs muss auch -ddas Trennzeichen setzen, damit Sie -d'\n'mit einer durch Zeilenumbrüche getrennten Liste umgehen können. Dies kann nützlich sein, wenn Sie eine Liste von Dateinamen in einer Datei usw. behandeln (sofern die Dateinamen keine haben) Zeilenumbrüche in ihnen, das heißt.)
ilkkachu
@ilkkachu: Ja, Zeilenumbrüche in Dateinamen sind viel seltener als Leerzeichen, da sie die meisten Skripte beschädigen. myfunc(){ local IFS=$'\n'; fgrep stdlib.h` $ (find) ; }funktioniert ebenfalls mit dem gleichen Effekt. Als (IFS=...; cmd...)Einzeiler kann eine Subshell auch die Änderung an IFS enthalten, ohne dass diese gespeichert / wiederhergestellt werden muss.
Peter Cordes
@PeterCordes Bitte mach keine command $( find )Sachen. Problematische Dateinamen mit Leerzeichen und Sonderzeichen können diese Art von Dingen beschädigen. Zumindest doppelte Anführungszeichen der Befehlsersetzung.
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy: Vielen Dank für den Hinweis, dass es so aussieht, als würde ich dies empfehlen. Personen, die überfliegen, haben das möglicherweise kopiert / eingefügt, anstatt den nächsten Abschnitt zu lesen. Aktualisiert, um dies zu beheben.
Peter Cordes
@SergiyKolodyazhnyy: Oder haben Sie auf meinen Kommentar geantwortet? Beachten Sie, dass ich es IFSso einstelle, dass es der Verwendung entspricht xargs '-d\n'. Glob-Erweiterung und Verarbeitung von Shell-Metazeichen erfolgen vor den Auswirkungen der Befehlssubstitution. Ich denke, dass dies auch bei Dateinamen mit $()oder sicher ist >. Einverstanden, dass die Verwendung der Wortteilung bei der Befehlsersetzung keine bewährte Methode ist, außer für die einmalige interaktive Verwendung, bei der Sie etwas über die Dateinamen wissen. command "$(find)"Ist aber nur dann sinnvoll, wenn Sie genau 1 Dateinamen erwarten ...
Peter Cordes
5

Wird im Allgemeinen xargsfür Fälle verwendet, in denen Sie (mit dem Symbol |) etwas von einem Befehl zum anderen ( Command1 | Command2) weiterleiten würden, die Ausgabe des ersten Befehls jedoch nicht korrekt als Eingabe für den zweiten Befehl empfangen wird.

Dies ist normalerweise der Fall, wenn der zweite Befehl die Dateneingabe über Standard In (stdin) nicht korrekt verarbeitet (z. B .: Mehrere Zeilen als Eingabe, die Art der Zeileneinrichtung, die als Eingabe verwendeten Zeichen, mehrere Parameter als Eingabe, der als empfangene Datentyp) Eingabe usw ..). Um Ihnen ein kurzes Beispiel zu geben, testen Sie Folgendes:

Beispiel 1:

ls | echo- Dies wird nichts bewirken, da er echonicht weiß, wie er mit den empfangenen Eingaben umgeht. In diesem Fall xargswird die Eingabe in diesem Fall so verarbeitet, dass sie korrekt verarbeitet werden kann echo(z. B .: als einzelne Informationszeile).

ls | xargs echo- Dadurch werden alle Informationen lsin einer einzigen Zeile ausgegeben

Beispiel 2:

Angenommen, ich habe mehrere goLang-Dateien in einem Ordner namens go. Ich würde sie mit so etwas suchen:

find go -name *.go -type f | echo- Aber wenn das Pipe-Symbol da und das echoam Ende wäre, würde es nicht funktionieren.

find go -name *.go -type f | xargs echo- Hier würde es dank funktionieren, xargsaber wenn ich jede Antwort vom findBefehl in einer einzigen Zeile haben möchte, würde ich Folgendes tun:

find go -name *.go -type f | xargs -0 echo- In diesem Fall wird die gleiche Ausgabe von findmit angezeigt echo.

Befehle wie cp, echo, rm, lessund andere, die einen besseren Umgang mit der Eingabe benötigen, erhalten einen Vorteil, wenn sie mit verwendet werden xargs.

Luis Alvarado
quelle
4

xargs wird zum automatischen Generieren von Befehlszeilenargumenten verwendet, die (normalerweise) auf einer Liste von Dateien basieren.

Überlegen Sie sich also einige Alternativen zur Verwendung des folgenden xargsBefehls:

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

Es gibt mehrere Gründe, es anstelle anderer Optionen zu verwenden, die ursprünglich in anderen Antworten nicht erwähnt wurden:

  1. find . -name '*.c' -exec grep 'stdlib.h' {}\;grepFür jede Datei wird ein Prozess generiert - dies wird im Allgemeinen als schlechte Vorgehensweise angesehen und kann das System stark belasten, wenn viele Dateien gefunden werden.
  2. Wenn viele Dateien vorhanden sind, schlägt ein grep 'stdlib.h' $(find . -name '*.c')Befehl wahrscheinlich fehl, da die Ausgabe der $(...)Operation die maximale Befehlszeilenlänge der Shell überschreitet

Wie in anderen Antworten erwähnt, ist der Grund für die Verwendung des -print0Arguments für finddieses Szenario und des -0Arguments für xargs, dass Dateinamen mit bestimmten Zeichen (z. B. Anführungszeichen, Leerzeichen oder sogar Zeilenumbrüche) weiterhin korrekt behandelt werden.

Michael Firth
quelle