Ausführen mehrerer Befehle mit xargs

310
cat a.txt | xargs -I % echo %

Im obigen Beispiel verwendet xargs echo %das Befehlsargument. In einigen Fällen benötige ich jedoch mehrere Befehle, um das Argument anstelle eines zu verarbeiten. Zum Beispiel:

cat a.txt | xargs -I % {command1; command2; ... }

Aber xargs akzeptiert dieses Formular nicht. Eine Lösung, die ich kenne, ist, dass ich eine Funktion zum Umschließen der Befehle definieren kann, aber es ist keine Pipeline, ich bevorzuge es nicht. Gibt es eine andere Lösung?

Dagang
quelle
1
Die meisten dieser Antworten sind Sicherheitslücken . Hier finden Sie eine möglicherweise gute Antwort.
Mateen Ulhaq
2
Ich benutze xargs für fast alles, aber ich hasse es, Befehle in Strings zu setzen und explizit Subshells zu erstellen. Ich bin kurz davor zu lernen, wie man in eine whileSchleife leitet, die mehrere Befehle enthalten kann.
Sridhar Sarnobat

Antworten:

443
cat a.txt | xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

... oder ohne nutzlosen Gebrauch von Katze :

<a.txt xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

Um einige der Feinheiten zu erklären:

  • Die Verwendung von "$arg"statt %(und das Fehlen von -Iin der xargsBefehlszeile) ist aus Sicherheitsgründen: Führen von Daten auf shder Argumente Liste Befehlszeile statt es in Code verhindert Inhalt ersetzt wird, dass Daten enthalten (wie zum Beispiel $(rm -rf ~)eines besonders nehmen böswilliges Beispiel) von der Ausführung als Code.

  • In ähnlicher Weise ist die Verwendung von -d $'\n'eine GNU-Erweiterung, die bewirkt xargs, dass jede Zeile der Eingabedatei als separates Datenelement behandelt wird. Entweder dies oder -0(was NULs anstelle von Zeilenumbrüchen erwartet) ist erforderlich, um zu verhindern, dass xargs versucht, Shell-ähnliche (aber nicht ganz Shell-kompatible) Parsing auf den gelesenen Stream anzuwenden . (Wenn Sie keine GNU-xargs haben, können Sie tr '\n' '\0' <a.txt | xargs -0 ...ohne zeilenorientiertes Lesen verwenden -d).

  • Das _ist ein Platzhalter für $0, sodass andere Datenwerte durch xargsWerden $1und Weiter hinzugefügt werden. Dies ist zufällig der Standardwertsatz, über den eine forSchleife iteriert.

Keith Thompson
quelle
58
Für diejenigen, die mit sh -c- nicht vertraut sind - beachten Sie, dass das Semikolon nach jedem Befehl nicht optional ist, selbst wenn es der letzte Befehl in der Liste ist.
Noah Sussman
6
Zumindest in meiner Konfiguration muss unmittelbar nach dem ersten "{" ein Leerzeichen stehen. Vor der abschließenden geschweiften Klammer ist kein Platz erforderlich, aber wie Herr Sussman feststellte, benötigen Sie ein schließendes Semikolon.
Willdye
4
Diese Antwort hatte zuvor geschweifte Klammern um command1und command2; Ich habe später festgestellt, dass sie nicht notwendig sind.
Keith Thompson
24
Um die obigen Kommentare zu Semikolons zu verdeutlichen, ist vor dem Schließen ein Semikolon erforderlich }: sh -c '{ command1; command2; }' -- but it's not required at the end of a command sequence that doesn't use braces: sh -c 'command1; command2'`
Keith Thompson
8
Wenn Sie das %Zeichen irgendwo in Ihre übergebene Zeichenfolge aufnehmen sh -c, ist dies anfällig für Sicherheitslücken: Ein Dateiname, der $(rm -rf ~)'$(rm -rf ~)'(und das ist eine völlig legale Teilzeichenfolge, die in einem Dateinamen auf gängigen UNIX-Dateisystemen enthalten sein muss) enthält, führt zu einem sehr schlechten Tag .
Charles Duffy
35

Mit GNU Parallel können Sie Folgendes tun:

cat a.txt | parallel 'command1 {}; command2 {}; ...; '

Sehen Sie sich die Intro-Videos an, um mehr zu erfahren: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Aus Sicherheitsgründen wird empfohlen, zur Installation Ihren Paketmanager zu verwenden. Wenn Sie dies jedoch nicht tun können, können Sie diese 10-Sekunden-Installation verwenden.

Bei der 10-Sekunden-Installation wird versucht, eine vollständige Installation durchzuführen. Wenn dies fehlschlägt, eine persönliche Installation. Wenn dies fehlschlägt, eine minimale Installation.

$ (wget -O - pi.dk/3 || lynx -source pi.dk/3 || curl pi.dk/3/ || \
   fetch -o - http://pi.dk/3 ) > install.sh
$ sha1sum install.sh | grep 3374ec53bacb199b245af2dda86df6c9
12345678 3374ec53 bacb199b 245af2dd a86df6c9
$ md5sum install.sh | grep 029a9ac06e8b5bc6052eac57b2c3c9ca
029a9ac0 6e8b5bc6 052eac57 b2c3c9ca
$ sha512sum install.sh | grep f517006d9897747bed8a4694b1acba1b
40f53af6 9e20dae5 713ba06c f517006d 9897747b ed8a4694 b1acba1b 1464beb4
60055629 3f2356f3 3e9c4e3c 76e3f3af a9db4b32 bd33322b 975696fc e6b23cfb
$ bash install.sh
Ole Tange
quelle
56
Das Installieren von Tools über das Ausführen von zufälligen Skripten von unbekannten Sites ist eine schreckliche Praxis. Parallel hat oficiall Pakete für beliebte Distributionen, denen (in gewissem Umfang) viel mehr vertraut werden kann als zufällige wget | sh ...
mdrozdziel
4
Lassen Sie uns sehen, was der einfachste Angriffsvektor ist: Pi.dk wird vom Autor von GNU Parallel gesteuert. Um also anzugreifen, müssten Sie in den Server eindringen oder DNS übernehmen. Um das offizielle Paket einer Distribution zu übernehmen, können Sie sich oft freiwillig melden, um das Paket zu pflegen. Obwohl Sie im Allgemeinen Recht haben, scheint Ihr Kommentar in diesem speziellen Fall nicht gerechtfertigt zu sein.
Ole Tange
10
In der Praxis weiß ich nicht, dass pi.dk dem Autor gehört. Es ist ein wenig Arbeit, zu überprüfen, ob dies der Fall ist, darüber nachzudenken, wie ssl in wget verwendet wird, und zu überprüfen, ob dieser Befehl das tut, was er tun soll. Ihr Standpunkt, dass das offizielle Paket schädlichen Code enthalten kann, ist wahr, aber das gilt auch für das wget-Paket.
Fabian
3
Dies ist möglicherweise nicht die beste Lösung, wenn jeder der Befehle, die OP ausführen möchte, sequentiell und korrekt sein muss.
IcarianComplex
2
@IcarianComplex Durch Hinzufügen von -j1 wird das behoben.
Ole Tange
26

Dies ist nur ein weiterer Ansatz ohne Xargs oder Cat:

while read stuff; do
  command1 "$stuff"
  command2 "$stuff"
  ...
done < a.txt
hmontoliu
quelle
2
Buggy, wie angegeben. Wenn Sie nicht löschen IFS, werden führende und nachfolgende Leerzeichen in den Dateinamen ignoriert. Wenn Sie keine -rDateinamen mit wörtlichen Backslashes hinzufügen , werden diese Zeichen ignoriert.
Charles Duffy
Beantwortet die Frage nicht. Es wurde speziell danach gefragt xargs. (Dies ist schwer zu erweitern, um etwas Ähnliches wie die GNU xargs- -P<n>Option zu tun )
Gert van den Berg
1
Das funktioniert einwandfrei. Sie können es auch als $ command | while read line; do c1 $line; c2 $line; done
Pipe-
25

Sie können verwenden

cat file.txt | xargs -i  sh -c 'command {} | command2 {} && command3 {}'

{} = Variable für jede Zeile in der Textdatei

Ossama
quelle
8
Das ist unsicher. Was ist, wenn Sie file.txtein Datum mit $(rm -rf ~)einem Teilstring enthalten?
Charles Duffy
19

Eine Sache, die ich tue, ist, diese Funktion zu .bashrc / .profile hinzuzufügen:

function each() {
    while read line; do
        for f in "$@"; do
            $f $line
        done
    done
}

dann kannst du Dinge wie tun

... | each command1 command2 "command3 has spaces"

Das ist weniger ausführlich als xargs oder -exec. Sie können die Funktion auch so ändern, dass der Wert aus dem Lesevorgang an einer beliebigen Stelle in den Befehlen jeweils eingefügt wird, wenn Sie dieses Verhalten ebenfalls benötigen.

mwm
quelle
1
Unterschätzte Antwort, das ist sehr praktisch
charlesreid1
15

Ich bevorzuge einen Stil, der einen Trockenlaufmodus (ohne | sh) ermöglicht:

cat a.txt | xargs -I % echo "command1; command2; ... " | sh

Funktioniert auch mit Rohren:

cat a.txt | xargs -I % echo "echo % | cat " | sh
brablc
quelle
2
Dies funktioniert, bis Sie die -POption von GNU xargs verwenden möchten ... (Wenn nicht, verwende ich meistens -execon find, da meine Eingaben meistens Dateinamen sind)
Gert van den Berg
13

Ein bisschen spät zur Party.

Ich verwende das folgende Format, um meine Verzeichnisse vor der Migration mit Tausenden winziger Dateien zu komprimieren. Wenn Sie keine einfachen Anführungszeichen in Befehlen benötigen, sollte dies funktionieren.

Mit einigen Änderungen bin ich sicher, dass es für jemanden nützlich sein wird. Getestet in Cygwin(babun)

find . -maxdepth 1 ! -path . -type d -print0 | xargs -0 -I @@ bash -c '{ tar caf "@@.tar.lzop" "@@" && echo Completed compressing directory "@@" ; }'

find .Hier finden
-maxdepth 1Gehen Sie nicht in
! -path .untergeordnete Verzeichnisse . / Aktueller Verzeichnispfad stimmt
-type dnur mit Verzeichnissen überein
-print0Ausgabe getrennt durch Null-Bytes \ 0
| xargsPipe to xargs
-0Eingabe ist null getrennte Bytes
-I @@Platzhalter ist @@. Ersetzen Sie @@ durch Eingabe.
bash -c '...'Run Bash Befehl
{...}Befehl Gruppierung
&&nur nächsten Befehl ausführen , wenn die vorherige Befehl beendete erfolgreich (exit 0)

Final ;ist wichtig, sonst schlägt es fehl.

Ausgabe:

Completed compressing directory ./Directory1 with meta characters in it
Completed compressing directory ./Directory2 with meta characters in it
Completed compressing directory ./Directory3 with meta characters in it

2018 Juli Update:

Wenn Sie Hacks lieben und herumspielen, ist hier etwas Interessantes:

echo "a b c" > a.txt
echo "123" >> a.txt
echo "###this is a comment" >> a.txt
cat a.txt
myCommandWithDifferentQuotes=$(cat <<'EOF'                                     
echo "command 1: $@"; echo 'will you do the fandango?'; echo "command 2: $@"; echo
EOF
)
< a.txt xargs -I @@ bash -c "$myCommandWithDifferentQuotes" -- @@

Ausgabe:

command 1: a b c
will you do the fandango?
command 2: a b c

command 1: 123
will you do the fandango?
command 2: 123

command 1: ###this is a comment
will you do the fandango?
command 2: ###this is a comment

Erläuterung:
- Erstellen Sie eine einzelne Liner Skript und speichern sie in einer Variablen
- xargs liest a.txtund führt es als bashSkript
- @@ stellt sicher , dass jedes Mal eine ganze Zeile übergeben wird
- Putting @@nach --stellt sicher , @@als Positionsparameter eingegeben genommen bashBefehl, kein bashStart OPTION, das heißt wie sich -cselbst, was bedeutetrun command

--ist magisch, es funktioniert mit vielen anderen Dingen, dh sshsogarkubectl

sdkks
quelle
1
Ich habe ein Setup mit dieser Art von Dingen verwendet: find . -type f -print0|xargs -r0 -n1 -P20 bash -c 'f="{}";ls -l "$f"; gzip -9 "$f"; ls -l "$f.gz"'(Es ist etwas einfacher beim Konvertieren von Schleifen)
Gert van den Berg
1
Späte Bearbeitung für meinen vorherigen Kommentar: (Wenn Dateinamen doppelte Anführungszeichen enthalten, liegt ein Sicherheitsproblem vor ...) (Dies scheint "$@"die einzige Möglichkeit zu sein, dies zu vermeiden ... (mit, -n1wenn Sie die Anzahl der Parameter begrenzen möchten))
Gert van den Berg
Es ist zu beachten, dass --die Shell verwendet, um zu sagen, dass keine Optionen mehr akzeptiert werden sollen. Dies ermöglicht es eine sein , -nach dem --zu. Sie können eine sehr interessante und verwirrende Ausgabe erhalten, wenn Sie dies nicht tun, z. B. grep -rwenn Sie es in das Muster aufnehmen -! Die Art und Weise, wie Sie es ausdrücken, erklärt dies jedoch nicht wirklich. Iirc, es ist eine POSIX-Sache, aber es lohnt sich trotzdem, darauf hinzuweisen, denke ich. Nur etwas zu beachten. Und ich liebe diesen Bonus übrigens!
Pryftan
10

Dies scheint die sicherste Version zu sein.

tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

( -0Kann entfernt und die werden trmit einer Umleitung ersetzt (oder die Datei kann mit einem Null - separierte Datei stattdessen ersetzt werden). Es ist vor allem dort , da ich hauptsächlich Verwendung xargsmit findmit -print0Ausgang) (Dies könnte auch relevant sein , auf xargsVersionen ohne die-0 Erweiterung)

Dies ist sicher, da args die Parameter bei der Ausführung als Array an die Shell übergibt. Die Shell bashwürde sie dann (zumindest ) als unverändertes Array an die anderen Prozesse übergeben, wenn alle unter Verwendung von erhalten werden["$@"][1]

Wenn Sie verwenden ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '', schlägt die Zuweisung fehl, wenn die Zeichenfolge doppelte Anführungszeichen enthält. Dies gilt für jede Variante mit -ioder -I. (Da es in eine Zeichenfolge ersetzt wird, können Sie jederzeit Befehle einfügen, indem Sie unerwartete Zeichen (wie Anführungszeichen, Backticks oder Dollarzeichen) in die Eingabedaten einfügen.)

Wenn die Befehle jeweils nur einen Parameter annehmen können:

tr '[\n]' '[\0]' < a.txt | xargs -r0 -n1 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

Oder mit etwas weniger Prozessen:

tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'for f in "$@"; do command1 "$f"; command2 "$f"; done;' ''

Wenn Sie GNU xargsoder eine andere mit der -PErweiterung haben und 32 Prozesse parallel ausführen möchten, mit jeweils nicht mehr als 10 Parametern für jeden Befehl:

tr '[\n]' '[\0]' < a.txt | xargs -r0 -n10 -P32 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

Dies sollte gegenüber Sonderzeichen in der Eingabe robust sein. (Wenn die Eingabe durch Null getrennt ist.) Die trVersion erhält eine ungültige Eingabe, wenn einige der Zeilen Zeilenumbrüche enthalten. Dies ist jedoch bei einer durch Zeilenumbrüche getrennten Datei unvermeidbar.

Der leere erste Parameter für bash -cist folgendermaßen: (Von der bashManpage ) (Danke @clacke)

-c   If the -c option is present, then  commands  are  read  from  the  first  non-option  argument  com
     mand_string.   If there are arguments after the command_string, the first argument is assigned to $0
     and any remaining arguments are assigned to the positional parameters.  The assignment  to  $0  sets
     the name of the shell, which is used in warning and error messages.
Gert van den Berg
quelle
Dies sollte auch bei doppelten Anführungszeichen in Dateinamen funktionieren. Das erfordert eine Shell, die richtig unterstützt"$@"
Gert van den Berg
Ihnen fehlt das Argument argv [0], um zu schlagen. bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
Clacke
3
Hier geht es nicht darum, was xargs tut. bashwith -cnimmt zuerst (nach den Befehlen) ein Argument, das der Name des Prozesses ist, und nimmt dann die Positionsargumente. Versuchen Sie zu bash -c 'echo "$@" ' 1 2 3 4sehen, was herauskommt.
Clacke
Es ist schön, eine sichere Version zu haben, die nicht Bobby-Tabled bekommt.
Mateen Ulhaq
8

Eine andere mögliche Lösung, die für mich funktioniert, ist so etwas wie -

cat a.txt | xargs bash -c 'command1 $@; command2 $@' bash

Beachten Sie die 'Bash' am Ende - ich gehe davon aus, dass sie als argv [0] an Bash übergeben wird. Ohne diese Syntax geht der erste Parameter für jeden Befehl verloren. Es kann ein beliebiges Wort sein.

Beispiel:

cat a.txt | xargs -n 5 bash -c 'echo -n `date +%Y%m%d-%H%M%S:` ; echo " data: " $@; echo "data again: " $@' bash
tavvit
quelle
5
Wenn Sie nicht zitieren "$@", teilen Sie die Argumentliste auf und erweitern sie global.
Charles Duffy
2

Mein aktuelles BKM dafür ist

... | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'

Es ist bedauerlich, dass hierfür Perl verwendet wird, das weniger wahrscheinlich installiert wird als Bash. Es werden jedoch mehr Eingaben verarbeitet als die akzeptierte Antwort. (Ich begrüße eine allgegenwärtige Version, die nicht auf Perl basiert.)

@ KeithThompsons Vorschlag von

 ... | xargs -I % sh -c 'command1; command2; ...'

ist großartig - es sei denn, Sie haben das Shell-Kommentarzeichen # in Ihrer Eingabe. In diesem Fall werden ein Teil des ersten Befehls und der gesamte zweite Befehl abgeschnitten.

Hashes # können häufig vorkommen, wenn die Eingabe aus einer Dateisystemliste wie ls oder find stammt und Ihr Editor temporäre Dateien mit # im Namen erstellt.

Beispiel für das Problem:

$ bash 1366 $>  /bin/ls | cat
#Makefile#
#README#
Makefile
README

Ups, hier ist das Problem:

$ bash 1367 $>  ls | xargs -n1 -I % sh -i -c 'echo 1 %; echo 2 %'
1
1
1
1 Makefile
2 Makefile
1 README
2 README

Ahh, das ist besser:

$ bash 1368 $>  ls | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'
1 #Makefile#
2 #Makefile#
1 #README#
2 #README#
1 Makefile
2 Makefile
1 README
2 README
$ bash 1369 $>  
Krazy Glew
quelle
5
# Problem kann leicht mit Anführungszeichen gelöst werden:ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
gpl