Esecuzione di più comandi con xargs


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

Nell'esempio sopra, xargs prende echo %come argomento il comando. Ma in alcuni casi, ho bisogno di più comandi per elaborare l'argomento anziché uno. Per esempio:

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

Ma xargs non accetta questo modulo. Una soluzione che conosco è che posso definire una funzione per avvolgere i comandi, ma non è una pipeline, non la preferisco. C'è un'altra soluzione?


1
La maggior parte di queste risposte sono vulnerabilità di sicurezza . Vedi qui per una risposta potenzialmente buona.
Mateen Ulhaq,

2
Uso xargs per quasi tutto, ma odio inserire comandi all'interno di stringhe e creare esplicitamente sottotitoli. Sono sul punto di imparare come convogliare in un whileciclo che può contenere più comandi.
Sridhar Sarnobat,

Risposte:


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

... o, senza un uso inutile del gatto :

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

Per spiegare alcuni dei punti più fini:

  • L'impiego di "$arg" invece di %(e l'assenza di -Inella xargsriga di comando) è per motivi di sicurezza: il passaggio dei dati shnell'elenco degli argomenti della riga di comando anziché la loro sostituzione in codice impedisce il contenuto che i dati potrebbero contenere (come $(rm -rf ~), per esempio malevolo) da eseguire come codice.

  • Allo stesso modo, l'uso di -d $'\n'è un'estensione GNU che provocaxargs trattamento di ogni riga del file di input come un elemento di dati separato. Questo o -0(che prevede NUL anziché nuove righe) è necessario per impedire agli xarg di provare ad applicare l' analisi simile a shell (ma non abbastanza compatibile con shell) allo stream che legge. (Se non si dispone di xarg GNU, è possibile utilizzare tr '\n' '\0' <a.txt | xargs -0 ...per ottenere la lettura orientata alla linea senza -d).

  • La _è un segnaposto $0, in modo che altri valori di dati aggiunto da xargsdiventare $1e poi, che risulta essere l'insieme predefinito di valori un forciclo itera sopra.


58
Per coloro che non hanno familiarità con sh -c- notare che il punto e virgola dopo ciascun comando non è facoltativo, anche se è l'ultimo comando nell'elenco.
Noah Sussman,

6
Almeno sulla mia configurazione, ci deve essere uno spazio immediatamente dopo il "{" iniziale. Non è necessario spazio prima della parentesi graffa finale, ma come ha notato il signor Sussman, è necessario un punto e virgola di chiusura.
Willdye,

4
Questa risposta precedentemente aveva parentesi graffe intorno command1e command2; In seguito ho capito che non erano necessari.
Keith Thompson,

24
Per chiarire i suddetti commenti sui punti e virgola, è richiesto un punto e virgola prima di una chiusura }: 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
Se stai compreso il %posto carattere nella stringa passata a sh -c, allora questo è soggetto a vulnerabilità di sicurezza: Un nome di file che contiene $(rm -rf ~)'$(rm -rf ~)'(e questa è una stringa perfettamente legale per avere all'interno di un nome di file su file system comuni UNIX!) Causerà a qualcuno una molto brutta giornata .
Charles Duffy,

35

Con GNU Parallel puoi fare:

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

Guarda i video introduttivi per saperne di più: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Per motivi di sicurezza, si consiglia di utilizzare il gestore pacchetti per l'installazione. Ma se non puoi farlo, puoi utilizzare questa installazione di 10 secondi.

L'installazione in 10 secondi proverà a eseguire un'installazione completa; se fallisce, un'installazione personale; se fallisce, un'installazione minima.

$ (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

56
L'installazione di strumenti tramite l'esecuzione di script casuali da siti sconosciuti è una pratica orribile. Parallel ha pacchetti oficiall per distribuzioni popolari, di cui ci si può fidare (in parte) molto più che wget casuali | sh ...
mdrozdziel

4
Vediamo qual è il vettore di attacco più semplice: Pi.dk è controllato dall'autore di GNU Parallel, quindi per attaccare dovresti entrare nel server o prendere il DNS. Per assumere il pacchetto ufficiale di una distribuzione, spesso puoi semplicemente offrirti volontario per mantenere il pacchetto. Quindi, anche se potresti avere ragione in generale, sembra che in questo caso particolare il tuo commento non sia giustificato.
Ole Tange,

10
In pratica non so che pi.dk appartiene all'autore. Verificare effettivamente che sia così, pensare a come usare ssl in wget e verificare che questo comando faccia quello che dovrebbe fare è un po 'di lavoro. Il punto che il pacchetto ufficiale può contenere codice dannoso è vero, ma vale anche per il pacchetto wget.
Fabian,

3
Questa potrebbe non essere la soluzione migliore se ciascuno dei comandi che OP vuole eseguire deve essere sequenziale, giusto?
IcarianComplex,

2
@IcarianComplex L'aggiunta di -j1 risolverà questo problema.
Ole Tange,

26

Questo è solo un altro approccio senza xargs né cat:

while read stuff; do
  command1 "$stuff"
  command2 "$stuff"
  ...
done < a.txt

2
Buggy, come indicato. A meno che tu non chiarisca IFS, ignorerà gli spazi bianchi iniziali e finali nei nomi dei file; a meno che non si aggiunge -r, i nomi dei file con backslash letterali avranno quei personaggi ignorati.
Charles Duffy,

Non risponde alla domanda Ha chiesto in particolare xargs. (Questo è difficile da espandere per fare qualcosa di simile all'opzione GNU xargs' -P<n>)
Gert van den Berg

1
Funziona perfettamente. Puoi anche usarlo come comando in pipe come$ command | while read line; do c1 $line; c2 $line; done
Alexar

25

Puoi usare

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

{} = variabile per ogni riga nel file di testo


8
Questo non è sicuro. Cosa succede se il tuo file.txtcontiene un dato con $(rm -rf ~)come sottostringa?
Charles Duffy,

19

Una cosa che faccio è aggiungere a .bashrc / .profile questa funzione:

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

allora puoi fare cose come

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

che è meno dettagliato di xargs o -exec. È inoltre possibile modificare la funzione per inserire il valore dalla lettura in una posizione arbitraria nei comandi a ciascuno, se fosse necessario anche quel comportamento.


1
Risposta sottovalutata, è estremamente utile
charlesreid1

15

Preferisco lo stile che consente la modalità di funzionamento a secco (senza | sh):

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

Funziona anche con tubi:

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

2
Questo funziona, fino a quando si desidera utilizzare GNU xargs' -Popzione ... (In caso contrario, mi usano per lo più -execsu find, dal momento che i miei ingressi sono per lo più nomi di file)
Gert van den Berg

13

Un po 'in ritardo alla festa.

Uso il formato seguente per comprimere le mie directory con migliaia di piccoli file prima della migrazione. Se non hai bisogno di virgolette singole all'interno dei comandi, dovrebbe funzionare.

Con qualche modifica, sono sicuro che sarà utile per qualcuno. Testato in Cygwin(babun)

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

find .Trova qui
-maxdepth 1Non andare nelle directory
! -path .secondarie Escludi. / Il percorso della directory corrente
-type dcorrisponde solo alle directory
-print0Output separato per byte nulli \ 0
| xargsPipe to xargs L'
-0input è null byte separati Il
-I @@segnaposto è @@. Sostituire @@ con input.
bash -c '...'Esegui comando Bash
{...}Raggruppamento di comandi
&& Esegue il comando successivo solo se il comando precedente è stato chiuso correttamente (uscita 0)

Finale ; è importante, altrimenti fallirà.

Produzione:

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

Aggiornamento di luglio 2018:

Se ami gli hack e il gioco, ecco qualcosa di interessante:

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" -- @@

Produzione:

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

Spiegazione:
- Creare un singolo script del liner e memorizzarlo in una variabile
- xargs Legge a.txted eseguirlo come bashscript
- @@ Accertarsi ogni volta che viene passata un'intera riga
- Il mettere @@dopo si --assicura che @@sia preso come parametro posizionale come input per il bashcomando, non un bashinizio OPTION, cioè come -cse stesso che significarun command

--è magico, funziona con molte altre cose, vale a dire ssh, anchekubectl


1
Ho usato una configurazione con questo tipo di cose: find . -type f -print0|xargs -r0 -n1 -P20 bash -c 'f="{}";ls -l "$f"; gzip -9 "$f"; ls -l "$f.gz"'(È leggermente più facile quando si convertono i loop)
Gert van den Berg

1
Modifica tardiva per il mio commento precedente: (Se i nomi di file contengono virgolette doppie c'è un problema di sicurezza ...) (Sembra che usare "$@"sia l'unico modo per evitarlo ... (con -n1se si desidera limitare il numero di parametri))
Gert van den Berg,

Va notato che --viene utilizzato dalla shell per dire che non devono essere accettate più opzioni. Ciò consente che ci sia anche un -dopo --. Puoi ottenere un output molto interessante e confuso se non lo fai, ad esempio grep -rquando includi nel modello -! Il modo in cui lo pronunci, tuttavia, non chiarisce questo non spiega davvero come funziona. Iirc è una cosa POSIX ma comunque vale la pena sottolinearlo, credo. Solo qualcosa da considerare. E adoro quel bonus tra l'altro!
Pryftan,

10

Questa sembra essere la versione più sicura.

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

( -0Può essere rimosso ed il trsostituito con un reindirizzamento (o il file può essere sostituito con un file separato da nullo invece). E 'soprattutto lì dato che uso principalmente xargscon findcon -print0uscita) (potrebbe anche essere rilevante sulla xargsversioni senza la-0 estensione)

È sicuro, poiché args passerà i parametri alla shell come array durante l'esecuzione. La shell (almeno bash) li passerebbe quindi come array inalterato agli altri processi quando tutti saranno ottenuti usando["$@"][1]

Se lo usi ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '', l'assegnazione fallirà se la stringa contiene virgolette doppie. Questo vale per ogni variante che utilizza -io -I. (A causa della sua sostituzione in una stringa, è sempre possibile inserire comandi inserendo caratteri imprevisti (come virgolette, backtick o segni di dollaro) nei dati di input)

Se i comandi possono accettare solo un parametro alla volta:

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

O con un po 'meno processi:

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

Se hai GNU xargso un altro con l' -Pestensione e vuoi eseguire 32 processi in parallelo, ognuno con non più di 10 parametri per ciascun comando:

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

Questo dovrebbe essere solido contro qualsiasi carattere speciale nell'input. (Se l'input è nullo separato.) La trversione otterrà un input non valido se alcune linee contengono newline, ma ciò è inevitabile con un file separato da newline.

Il primo parametro vuoto per bash -cè dovuto a questo: (Dalla bashpagina man ) (Grazie @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.

Questo dovrebbe funzionare anche con doppie virgolette nei nomi dei file. Ciò richiede una shell che supporti correttamente"$@"
Gert van den Berg,

Manca l'argomento argv [0] per bash. bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
clacke,

3
Non si tratta di ciò che fa xargs. bashcon -caccetta prima (dopo i comandi) un argomento che sarà il nome del processo, quindi accetta gli argomenti posizionali. Prova a bash -c 'echo "$@" ' 1 2 3 4vedere cosa viene fuori.
clacke

È bello avere una versione sicura senza Bobby-Tabled.
Mateen Ulhaq,

8

Un'altra possibile soluzione che funziona per me è qualcosa di simile -

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

Nota 'bash' alla fine - suppongo che sia passato come argv [0] a bash. Senza di essa in questa sintassi il primo parametro di ciascun comando viene perso. Può essere una parola qualsiasi.

Esempio:

cat a.txt | xargs -n 5 bash -c 'echo -n `date +%Y%m%d-%H%M%S:` ; echo " data: " $@; echo "data again: " $@' bash

5
Se non citate "$@", allora state suddividendo le stringhe e espandendo glob l'elenco degli argomenti.
Charles Duffy,

2

Il mio attuale BKM per questo è

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

È un peccato che questo usi perl, che ha meno probabilità di essere installato rispetto a bash; ma gestisce più input che la risposta accettata. (Accolgo con favore una versione onnipresente che non si basa su perl.)

@ Il suggerimento di KeithThompson di

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

è ottimo - a meno che tu non abbia il carattere di commento della shell # nel tuo input, nel qual caso parte del primo comando e tutto il secondo comando verranno troncati.

Gli hash # possono essere abbastanza comuni, se l'input è derivato da un elenco di filesystem, come ls o find, e il tuo editor crea file temporanei con # nel loro nome.

Esempio del problema:

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

Oops, ecco il problema:

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

Ah, è meglio:

$ 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 $>  

5
# il problema può essere facilmente risolto usando le virgolette:ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
gpl
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.