Lassen Sie xargs den Befehl einmal für jede Eingabezeile ausführen

341

Wie kann ich xargs veranlassen, den Befehl für jede angegebene Eingabezeile genau einmal auszuführen? Standardmäßig werden die Zeilen aufgeteilt und der Befehl einmal ausgeführt, wobei jeder Zeile mehrere Zeilen übergeben werden.

Von http://en.wikipedia.org/wiki/Xargs :

find / path -type f -print0 | xargs -0 rm

In diesem Beispiel speist find die Eingabe von xargs mit einer langen Liste von Dateinamen. xargs teilt diese Liste dann in Unterlisten auf und ruft rm für jede Unterliste einmal auf. Dies ist effizienter als diese funktional äquivalente Version:

find / path -type f -exec rm '{}' \;

Ich weiß, dass find das Flag "exec" hat. Ich zitiere nur ein anschauliches Beispiel aus einer anderen Ressource.

Schreibgeschützt
quelle
4
In dem Beispiel, das Sie zur Verfügung stellen, find /path -type f -deletewäre noch effizienter :)
tzot
versuche keine xargs zu benutzen ...
Naib
6
OP, ich weiß, dass diese Frage sehr alt ist, aber sie taucht immer noch bei Google auf und meiner Meinung nach ist die akzeptierte Antwort falsch. Siehe meine längere Antwort unten.
Tobia
Bitte erwägen Sie, Ihre Annahme auf die Antwort von @ Tobia umzustellen, die viel besser ist. Die akzeptierte Antwort behandelt keine Leerzeichen in Namen und lässt nicht mehrere Argumente für den Befehl xargs zu, der eines der Hauptmerkmale von xargs ist.
Grau

Antworten:

391

Folgendes funktioniert nur, wenn Ihre Eingabe keine Leerzeichen enthält:

xargs -L 1
xargs --max-lines=1 # synonym for the -L option

von der Manpage:

-L max-lines
          Use at most max-lines nonblank input lines per command line.
          Trailing blanks cause an input line to be logically continued  on
          the next input line.  Implies -x.
Draemon
quelle
13
Für mich kann es so aussehen, als hätte xargs -n 1die von Ihnen angegebene "Argumentliste zu lang" gezeigt.
Wernight
19
Wenn MAX-LINESweggelassen wird, wird standardmäßig 1 verwendet, xargs -lwas ausreicht. Siehe info xargs.
Thor
3
@Wernight: "-n1" gibt nicht 1 Aufruf pro Eingabezeile. Vielleicht war Ihre Eingabezeile zu lang. Demo : echo "foo bar" | xargs -n1 echo. Wenn Sie also Dinge wie 'ls' einpfeifen, werden Leerzeichen nicht gut verarbeitet.
Gatoatigrado
8
Das ist falsch. -L 1beantwortet die ursprüngliche Frage nicht und -n 1dies nur in einer der möglichen Interpretationen. Siehe meine lange Antwort unten.
Tobia
2
@Tobia: Es beantwortet die ursprüngliche Frage, bei der es ganz speziell um Eingabezeilen ging. Genau das -L 1macht es. Für mich schien das OP eindeutig zu versuchen, das Standard-Chunking-Verhalten zu vermeiden, und da dies akzeptiert wurde, gehe ich davon aus, dass ich Recht hatte. Ihre Antwort befasst sich mit einem etwas anderen Anwendungsfall, in dem Sie auch das Chunking-Verhalten wünschen.
Draemon
206

Mir scheint, dass alle auf dieser Seite vorhandenen Antworten falsch sind, einschließlich der als richtig gekennzeichneten. Das liegt daran, dass die Frage nicht eindeutig formuliert ist.

Zusammenfassung:   Wenn Sie den Befehl "genau einmal für jede angegebene Eingabezeile" ausführen möchten und die gesamte Zeile (ohne die neue Zeile) als einzelnes Argument an den Befehl übergeben möchten , ist dies die beste UNIX-kompatible Methode:

... | tr '\n' '\0' | xargs -0 -n1 ...

GNU verfügt xargsmöglicherweise über nützliche Erweiterungen, mit trdenen Sie diese beseitigen können. Sie sind jedoch unter OS X und anderen UNIX-Systemen nicht verfügbar.

Nun zur langen Erklärung…


Bei der Verwendung von xargs sind zwei Punkte zu berücksichtigen:

  1. Wie teilt es die Eingabe in "Argumente" auf? und
  2. Wie viele Argumente müssen gleichzeitig den untergeordneten Befehl übergeben werden?

Um das Verhalten von xargs zu testen, benötigen wir ein Dienstprogramm, das anzeigt, wie oft es ausgeführt wird und mit wie vielen Argumenten. Ich weiß nicht, ob es dafür ein Standarddienstprogramm gibt, aber wir können es ganz einfach in bash codieren:

#!/bin/bash
echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo

Angenommen, Sie speichern es showin Ihrem aktuellen Verzeichnis und machen es ausführbar. So funktioniert es:

$ ./show one two 'three and four'
-> "one" "two" "three and four" 

Wenn es sich bei der ursprünglichen Frage wirklich um Punkt 2 oben handelt (wie ich denke, nachdem ich sie einige Male gelesen habe) und sie so zu lesen ist (Änderungen in Fettdruck):

Wie kann ich xargs den Befehl genau einmal für jedes ausführen Argument gegeben Eingabe? Das Standardverhalten besteht darin, die Eingabe in Argumente zu unterteilen und den Befehl so oft wie möglich auszuführen , wobei jeder Instanz mehrere Argumente übergeben werden.

dann ist die Antwort -n 1.

Vergleichen wir das Standardverhalten von xargs, bei dem die Eingabe um Leerzeichen aufgeteilt wird und der Befehl so oft wie möglich aufgerufen wird:

$ echo one two 'three and four' | xargs ./show 
-> "one" "two" "three" "and" "four" 

und sein Verhalten mit -n 1:

$ echo one two 'three and four' | xargs -n 1 ./show 
-> "one" 
-> "two" 
-> "three" 
-> "and" 
-> "four" 

Wenn sich die ursprüngliche Frage andererseits auf Punkt 1 bezog und die Aufteilung der Eingaben so war (viele Leute, die hierher kommen, scheinen zu glauben, dass dies der Fall ist, oder verwechseln die beiden Probleme):

Wie kann ich xargs veranlassen, den Befehl mit genau einem Argument für jede angegebene Eingabezeile auszuführen ? Das Standardverhalten besteht darin, die Zeilen um Leerzeichen zu teilen .

dann ist die Antwort subtiler.

Man würde denken, dass -L 1dies hilfreich sein könnte, aber es stellt sich heraus, dass es das Parsen von Argumenten nicht ändert. Der Befehl wird für jede Eingabezeile nur einmal ausgeführt, mit so vielen Argumenten, wie in dieser Eingabezeile vorhanden waren:

$ echo $'one\ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" 
-> "two" 
-> "three" "and" "four" 

Nicht nur das, sondern wenn eine Zeile mit Leerzeichen endet, wird sie an die nächste angehängt:

$ echo $'one \ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" "two" 
-> "three" "and" "four" 

Es geht natürlich -Lnicht darum, die Art und Weise zu ändern, in der xargs die Eingabe in Argumente aufteilt.

Das einzige Argument, das dies plattformübergreifend tut (ausgenommen GNU-Erweiterungen), ist -0die Aufteilung der Eingabe auf NUL-Bytes.

Dann geht es nur noch darum, Zeilenumbrüche mit Hilfe von tr: in NUL zu übersetzen :

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 ./show 
-> "one " "two" "three and four" 

Jetzt sieht die Argumentanalyse gut aus, einschließlich des nachgestellten Leerzeichens.

Wenn Sie diese Technik mit kombinieren -n 1, erhalten Sie genau eine Befehlsausführung pro Eingabezeile, unabhängig von Ihrer Eingabe. Dies kann eine weitere Möglichkeit sein, die ursprüngliche Frage zu betrachten (möglicherweise die intuitivste, wenn man den Titel bedenkt):

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 
Tobia
quelle
sieht so aus ist die bessere Antwort. Ich verstehe jedoch immer noch nicht ganz, was der Unterschied zwischen -L und -n ist. Können Sie etwas mehr erklären?
Olala
5
@olala -Lführt den Befehl einmal pro Eingabezeile aus (ein Leerzeichen am Ende einer Zeile verbindet ihn jedoch mit der nächsten Zeile, und die Zeile wird weiterhin nach Leerzeichen in Argumente aufgeteilt). while -nführt den Befehl einmal pro Eingabeargument aus. Wenn Sie die Anzahl ->in den Ausgabebeispielen zählen, ist dies die Häufigkeit, mit der das Skript ./showausgeführt wird.
Tobia
Aha! Ich habe nicht bemerkt, dass ein Leerzeichen am Ende einer Zeile mit der nächsten Zeile verbunden wird. Vielen Dank!
Olala
4
GNU xargskann nützliche Erweiterungen haben oder nicht, die es Ihnen ermöglichen, es zu beseitigen.tr Es hat eine so sehr nützliche Erweiterung; from xargs --help- -d, --delimiter = CHARACTER-Elemente im Eingabestream werden durch CHARACTER und nicht durch Leerzeichen getrennt. deaktiviert die Quotierungs- und Backslash-Verarbeitung und die logische EOF-Verarbeitung
Piotr Dobrogost
Diese Antwort scheint verwirrt zu sein -L. -LGibt nicht an, wie oft das Skript pro Zeile ausgeführt werden soll, sondern wie viele Zeilen mit Eingabedaten gleichzeitig verbraucht werden sollen.
Moberg
22

Wenn Sie den Befehl für jede Zeile (dh das Ergebnis) ausführen möchten, von der Sie kommen find, wofür benötigen Sie dann den Befehl xargs?

Versuchen:

find Pfad -type f -exec dein-Befehl {} \;

Dabei wird das Literal {}durch den Dateinamen ersetzt und das Literal \;wird benötigt, um findzu wissen, dass der benutzerdefinierte Befehl dort endet.

BEARBEITEN:

(nach der Bearbeitung Ihrer Frage klarstellen, dass Sie wissen -exec)

Von man xargs:

-L max-Zeilen
Verwenden Sie höchstens max-Zeilen nicht leere Eingabezeilen pro Befehlszeile. Nachgestellte Leerzeichen bewirken, dass eine Eingabezeile in der nächsten Eingabezeile logisch fortgesetzt wird. Impliziert -x.

Beachten Sie, dass Dateinamen, die mit Leerzeichen enden, Probleme verursachen können, wenn Sie Folgendes verwenden xargs:

$ mkdir /tmp/bax; cd /tmp/bax
$ touch a\  b c\  c
$ find . -type f -print | xargs -L1 wc -l
0 ./c
0 ./c
0 total
0 ./b
wc: ./a: No such file or directory

Wenn Sie sich also nicht für die -execOption interessieren , verwenden Sie besser -print0und -0:

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a
tzot
quelle
17

Wie kann ich xargs veranlassen, den Befehl für jede angegebene Eingabezeile genau einmal auszuführen?

-L 1ist die einfache Lösung, funktioniert aber nicht, wenn eine der Dateien Leerzeichen enthält. Dies ist eine Schlüsselfunktion des -print0Arguments von find - um die Argumente durch '\ 0' anstelle von Leerzeichen zu trennen. Hier ist ein Beispiel:

echo "file with space.txt" | xargs -L 1 ls
ls: file: No such file or directory
ls: with: No such file or directory
ls: space.txt: No such file or directory

Eine bessere Lösung besteht darin tr, Zeilenumbrüche in null ( \0) Zeichen zu konvertieren und dann das xargs -0Argument zu verwenden. Hier ist ein Beispiel:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 ls
file with space.txt

Wenn Sie dann die Anzahl der Aufrufe begrenzen müssen, können Sie das -n 1Argument verwenden, um das Programm für jede Eingabe einmal aufzurufen:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 -n 1 ls

Auf diese Weise können Sie auch die Ausgabe von find filtern, bevor Sie die Unterbrechungen in Nullen konvertieren.

find . -name \*.xml | grep -v /target/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar
Grau
quelle
1
Es gibt einen Syntaxfehler im zweiten Codeblock tr '\ n' '\ 0 \ => tr' \ n '' \ 0 '. Ich habe versucht, dies zu beheben, aber "Bearbeitungen müssen mindestens 6 Zeichen lang sein" (dies scheint dumm wie ein Idiot, der sich weigert zu begehen, weil meine Änderung weniger als 6 Zeichen
betrug
1
Was bedeutet dies: "Ein weiteres Problem bei der Verwendung -Lbesteht darin, dass nicht für jeden xargsBefehlsaufruf mehrere Argumente zulässig sind."
Moberg
Ich habe meine Antwort verbessert, um diese überflüssigen Informationen @Moberg zu entfernen.
Grau
11

Eine andere Alternative ...

find /path -type f | while read ln; do echo "processing $ln"; done
Richard
quelle
9

Diese beiden Methoden funktionieren auch für andere Befehle, die find nicht verwenden!

xargs -I '{}' rm '{}'
xargs -i rm '{}'

Anwendungsbeispiel:

find . -name "*.pyc" | xargs -i rm '{}'

löscht alle pyc-Dateien in diesem Verzeichnis, auch wenn die pyc-Dateien Leerzeichen enthalten.

Alex Riedler
quelle
Dies gibt einen Dienstprogrammaufruf für jedes Element aus, das nicht optimal ist.
Grau
7
find path -type f | xargs -L1 command 

ist alles, was du brauchst.


quelle
4

Der folgende Befehl findet alle Dateien (-typ f) in /pathund kopiert sie dann mit cpin den aktuellen Ordner. Beachten Sie die Verwendung von if -I %, um ein Platzhalterzeichen in der cpBefehlszeile anzugeben , damit Argumente nach dem Dateinamen platziert werden können.

find /path -type f -print0 | xargs -0 -I % cp % .

Getestet mit Xargs (GNU findutils) 4.4.0


quelle
2

Sie können die Anzahl der Zeilen oder Argumente (wenn zwischen den einzelnen Argumenten Leerzeichen stehen) mit den Flags --max-lines bzw. --max-args begrenzen.

  -L max-lines
         Use at most max-lines nonblank input lines per command line.  Trailing blanks cause an input line to be logically continued on the next  input
         line.  Implies -x.

  --max-lines[=max-lines], -l[max-lines]
         Synonym  for  the -L option.  Unlike -L, the max-lines argument is optional.  If max-args is not specified, it defaults to one.  The -l option
         is deprecated since the POSIX standard specifies -L instead.

  --max-args=max-args, -n max-args
         Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see  the  -s  option)  is  exceeded,
         unless the -x option is given, in which case xargs will exit.
Schreibgeschützt
quelle
0

Es scheint, dass ich nicht genug Ruf habe, um Tobias Antwort oben einen Kommentar hinzuzufügen, also füge ich diese "Antwort" hinzu, um denjenigen von uns zu helfen, die xargsauf den Windows-Plattformen auf die gleiche Weise experimentieren möchten .

Hier ist eine Windows-Batch-Datei, die dasselbe tut wie Tobias schnell codiertes "Show" -Skript:

@echo off
REM
REM  cool trick of using "set" to echo without new line
REM  (from:  http://www.psteiner.com/2012/05/windows-batch-echo-without-new-line.html)
REM
if "%~1" == "" (
    exit /b
)

<nul set /p=Args:  "%~1"
shift

:start
if not "%~1" == "" (
    <nul set /p=, "%~1"
    shift
    goto start
)
echo.
CrashNeb
quelle
0

@ Draemon-Antworten scheinen mit "-0" auch bei Platz in der Datei richtig zu sein.

Ich habe den Befehl xargs ausprobiert und festgestellt, dass "-0" perfekt mit "-L" funktioniert. Sogar die Leerzeichen werden behandelt (wenn die Eingabe mit Null abgeschlossen wurde). Das Folgende ist ein Beispiel:

#touch "file with space"
#touch "file1"
#touch "file2"

Im Folgenden werden die Nullen aufgeteilt und der Befehl für jedes Argument in der Liste ausgeführt:

 #find . -name 'file*' -print0 | xargs -0 -L1
./file with space
./file1
./file2

führt also -L1das Argument für jedes nullterminierte Zeichen aus, wenn es mit "-0" verwendet wird. Um den Unterschied zu sehen, versuchen Sie:

 #find . -name 'file*' -print0 | xargs -0 | xargs -L1
 ./file with space ./file1 ./file2

Auch dies wird einmal ausgeführt:

 #find . -name 'file*' -print0  | xargs -0  | xargs -0 -L1
./file with space ./file1 ./file2

Der Befehl wird einmal ausgeführt, da "-L" jetzt nicht auf Null-Byte aufgeteilt wird. Sie müssen sowohl "-0" als auch "-L" angeben, um zu arbeiten.

Mohammad Karmi
quelle
-3

In Ihrem Beispiel besteht der Punkt beim Weiterleiten der Ausgabe von find an xargs darin, dass das Standardverhalten der Option -exec von find darin besteht, den Befehl für jede gefundene Datei einmal auszuführen. Wenn Sie find verwenden und das Standardverhalten wünschen, ist die Antwort einfach - verwenden Sie zunächst keine xargs.

Sherm Pendley
quelle
Was ich aus den Änderungen des OP implizieren kann , ist, dass die Eingabedaten nichts damit zu tun haben find, und deshalb bevorzugen sie die -execOption nicht.
tzot
-3

Führen Sie ant task clean-all für jede build.xml im aktuellen Ordner oder Unterordner aus.

find . -name 'build.xml' -exec ant -f {} clean-all \;
sergiofbsilva
quelle
Nicht jeder hat antinstalliert.
Grau