web-dev-qa-db-fra.com

Appel de plusieurs commandes via xargs

cat a.txt | xargs -I % echo %

Dans l'exemple ci-dessus, xargs prend echo % comme argument de la commande. Mais dans certains cas, j'ai besoin de plusieurs commandes pour traiter l'argument au lieu d'une. Par exemple:

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

Mais xargs n'accepte pas ce formulaire. Une solution que je connais est que je peux définir une fonction pour envelopper les commandes, mais ce n'est pas un pipeline, je ne le préfère pas. Y a-t-il une autre solution?

240
Dagang
cat a.txt | xargs -I % sh -c 'command1; command2; ...'

Notez que ceci est un Utilisation inutile de chat . Je l'écrirais comme:

< a.txt xargs -I % sh -c 'command1; command2; ...'

(Oui, la redirection peut être au début de la commande.)

Vraisemblablement, command1 et/ou command2 contiendront un ou plusieurs caractères %; sinon, il n'y aurait pas grand intérêt à utiliser l'option -I % pour xargs.

360
Keith Thompson

Avec GNU parallèle, vous pouvez faire:

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

Regardez les vidéos d'introduction pour en savoir plus: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

27
Ole Tange

Ceci est juste une autre approche sans xargs ni cat:

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

Vous pouvez utiliser

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

{} = variable pour chaque ligne du fichier texte

16
Ossama

Une chose que je fais est d'ajouter à .bashrc/.profile cette fonction:

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

alors vous pouvez faire des choses comme

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

qui est moins verbeux que xargs ou -exec. Vous pouvez également modifier la fonction pour insérer la valeur de la lecture à un emplacement arbitraire dans les commandes, si vous avez également besoin de ce comportement.

14
mwm

Je préfère le style qui permet le mode de fonctionnement à sec (sans | sh):

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

Fonctionne aussi avec des pipes:

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

Un peu tard pour la fête. 

J'utilise le format ci-dessous pour compresser mes répertoires avec des milliers de fichiers minuscules avant de migrer. Si vous n'avez pas besoin de guillemets simples dans les commandes, cela devrait fonctionner.

Avec quelques modifications, je suis sûr que ce sera utile pour quelqu'un. Testé dans Cygwin (babun)

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

find . Trouvez ici
-maxdepth 1 Ne pas aller dans les répertoires enfants
! -path . Exclude./Chemin du répertoire actuel
-type d ne correspondent qu'aux répertoires
-print0 Sortie séparée par des octets nuls\0
| xargs Pipe to xargs
-0 L'entrée est composée d'octets séparés par NULL
-I @@ Placeholder est @@. Remplacez @@ par une entrée.
bash -c '...' Commande Run Bash
{...} Groupement de commandes
&& N'exécute la commande suivante que si la commande précédente a été sortie avec succès (exit 0) 

Le ; final est important, sinon il échouera.

Sortie:

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

Mise à jour de juillet 2018:

Si vous aimez les hacks et les jeux, voici quelque chose d'intéressant:

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

Sortie:

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

Explication:
- Créer un script simple et le stocker dans une variable
- xargs lit a.txt et l'exécute en tant que script bash
- @@ s'assure chaque fois qu'une ligne entière est passée
- Mettre @@ après -- permet de s’assurer que @@ est pris comme entrée de paramètre de position dans la commande bash et non comme une variable bash start OPTION, c’est-à-dire comme -c lui-même qui signifie run command

-- est magique, il fonctionne avec beaucoup d’autres choses, par exemple ssh, même kubectl

6
sdkks

Une autre solution possible qui fonctionne pour moi est quelque chose comme:

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

Notez le bash à la fin - je suppose qu'il est passé comme argv [0] à bash. Sans cette syntaxe, le premier paramètre de chaque commande est perdu. Ce peut être n'importe quel mot.

Exemple:

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

Mon BKM actuel pour cela est

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

Il est regrettable que cela utilise Perl, qui est moins susceptible d’être installé que bash; mais il gère plus d’entrée que la réponse acceptée. (Je me félicite d'une version omniprésente qui ne repose pas sur Perl.)

La suggestion de @ KeithThompson de 

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

is great - sauf si vous avez le caractère de commentaire Shell dans votre entrée, auquel cas une partie de la première commande et toutes les secondes seront tronquées.

Les hachages # peuvent être assez courants, si l'entrée est dérivée d'une liste de système de fichiers, telle que ls ou find, et que votre éditeur crée des fichiers temporaires avec le nom #.

Exemple du problème:

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

Oops, voici le problème:

$ 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, c'est mieux:

$ 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 $>  
1
Krazy Glew

Cela semble être la version la plus sûre.

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

(-0 peut être supprimé et la tr remplacée par une redirection (ou le fichier peut être remplacé par un fichier séparé à la place). Il est principalement présent car j’utilise principalement xargs avec find with -print0 output) (Cela pourrait également être pertinent sur les versions xargs sans l'extension -0)

C’est sûr, car args transmettra les paramètres au shell en tant que tableau pour l’exécuter. Le shell (au moins bash) les transmettrait ensuite aux autres processus sous forme de tableau non modifié lorsque tous seraient obtenus à l'aide de ["$@"][1]

Si vous utilisez ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";', l'affectation échouera si la chaîne contient des guillemets doubles. Ceci est vrai pour chaque variante utilisant -i ou -I. (En raison de son remplacement dans une chaîne, vous pouvez toujours injecter des commandes en insérant des caractères inattendus (comme des guillemets, des backticks ou des signes dollar) dans les données d'entrée)

Si les commandes ne peuvent prendre qu'un paramètre à la fois:

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

Ou avec un peu moins de processus:

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

Si vous avez GNU xargs ou un autre avec l'extension -P et si vous souhaitez exécuter 32 processus en parallèle, chacun ne contenant pas plus de 10 paramètres pour chaque commande:

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

Cela devrait être robuste contre tous les caractères spéciaux dans l'entrée. (Si l'entrée est séparée de zéro.) La version tr obtiendra une entrée non valide si certaines lignes contiennent des nouvelles lignes, mais cela est inévitable avec un fichier séparé par une nouvelle ligne.

0
Gert van den Berg