455 Stimmen

Mehrere Befehle mit xargs ausführen

cat a.txt | xargs -I % echo %

Im obigen Beispiel, xargs nimmt echo % als Befehlsargument. Aber in manchen Fällen brauche ich mehrere Befehle, um das Argument zu verarbeiten, statt nur einen. 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 definieren kann, um die Befehle zu verpacken, aber das möchte ich vermeiden, weil es komplex ist. Gibt es eine bessere Lösung?

24voto

Gert van den Berg Punkte 1881

Dies scheint die sicherste Version zu sein.

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

( -0 kann entfernt werden und die tr durch eine Weiterleitung ersetzt werden (oder die Datei kann stattdessen durch eine durch Null getrennte Datei ersetzt werden). Es ist hauptsächlich dort drin, da ich hauptsächlich xargs con find con -print0 Ausgabe) (Dies könnte auch relevant sein für xargs Versionen ohne den -0 Verlängerung)

Dies ist sicher, da args die Parameter bei der Ausführung als Array an die Shell weitergibt. Die Shell (zumindest bash ) würde sie dann als unverändertes Array an die anderen Prozesse weitergeben, wenn alle mit ["$@"][1]

Wenn Sie ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '' enthält, schlägt die Zuweisung fehl, wenn die Zeichenfolge doppelte Anführungszeichen enthält. Dies gilt für jede Variante mit -i または -I . (Da es in eine Zeichenkette ersetzt wird, können Sie immer 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 xargs oder eine andere mit dem -P Erweiterung und Sie wollen 32 Prozesse parallel laufen lassen, mit 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 alle Sonderzeichen in der Eingabe gefeit sein. (Wenn die Eingabe durch Null getrennt ist.) Die tr Version wird einige ungültige Eingaben erhalten, wenn einige der Zeilen Zeilenumbrüche enthalten, aber das ist bei einer durch Zeilenumbrüche getrennten Datei unvermeidlich.

Der leere erste Parameter für bash -c ist darauf zurückzuführen: (Aus dem bash Manpage ) (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.

20voto

mwm Punkte 315

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 können Sie Dinge tun wie

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

was weniger ausführlich ist als xargs oder -exec. Sie könnten die Funktion auch so ändern, dass sie den Wert aus dem Lesevorgang an einer beliebigen Stelle in die einzelnen Befehle einfügt, wenn Sie dieses Verhalten ebenfalls benötigen.

12voto

tavvit Punkte 251

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

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

Beachten Sie das "bash" am Ende - ich nehme an, es wird als argv[0] an die Bash übergeben. Ohne ihn geht in dieser Syntax der erste Parameter jedes Befehls 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

3voto

Krazy Glew Punkte 6728

Mein derzeitiges BKM hierfür ist

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

Es ist bedauerlich, dass dies Perl verwendet, das weniger wahrscheinlich installiert ist als Bash; aber es verarbeitet mehr Eingaben als die akzeptierte Antwort. (Ich begrüße eine allgegenwärtige Version, die nicht auf Perl angewiesen ist).

@KeithThompsons Vorschlag von

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

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

Hashes # können recht häufig vorkommen, wenn die Eingabe von einem Dateisystem-Listing wie ls oder find abgeleitet wird 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 liegt 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 $>

2voto

Erik Berg Punkte 29

Versuchen Sie dies:

git config --global alias.all '!f() { find . -d -name ".git" | sed s/\\/\.git//g | xargs -P10 -I{} git --git-dir={}/.git --work-tree={} $1; }; f'

Er lässt zehn Threads parallel laufen und führt jeden beliebigen Git-Befehl für alle Repos in der Ordnerstruktur aus. Dabei spielt es keine Rolle, ob das Repository eine oder n Ebenen tief ist.

z.B: git all pull

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X