Appel de fonctions shell avec xargs

168

J'essaie d'utiliser xargs pour appeler une fonction plus complexe en parallèle.

#!/bin/bash
echo_var(){
    echo $1
    return 0
}
seq -f "n%04g" 1 100 |xargs -n 1 -P 10 -i echo_var {} 
exit 0

Cela renvoie l'erreur

xargs: echo_var: No such file or directory

Toute idée sur la façon dont je peux utiliser xargs pour accomplir cela, ou toute autre solution serait la bienvenue.

fac3
la source
2
Danger, utilisateur1148366, Danger! N'utilisez pas bash pour la programmation parallèle - vous rencontrerez tellement de problèmes. Utilisez C / C ++ et pthreads, ou des threads Java, ou tout ce qui vous fait réfléchir longuement à ce que vous faites, car la programmation parallèle demande beaucoup de réflexion pour bien fonctionner.
David Souther
27
@DavidSouther Si les tâches sont indépendantes, telles que la conversion de tous ces fichiers image en png, ne vous inquiétez pas. C'est lorsque vous avez la synchronisation (au-delà d'attendre que tout se termine) et la communication que cela devient compliqué.
ctrl-alt-delor
@DavidSouther - Je suis un développeur Java de longue date et j'ai travaillé dans groovy ces derniers temps. Et je continue à dire aux gens: les amis ne laissent pas les amis écrire un script bash. Et pourtant, je me retrouve à regarder ce post / solution parce que (visage triste :() Je suis engagé dans le traitement parallèle dans bash. Je pourrais facilement le faire dans groovy / java. Mauvais!
Christian Bongiorno
Également discuté dans unix.stackexchange.com/questions/158564/…
Joshua Goldberg

Réponses:

172

L'exportation de la fonction devrait le faire (non testé):

export -f echo_var
seq -f "n%04g" 1 100 | xargs -n 1 -P 10 -I {} bash -c 'echo_var "$@"' _ {}

Vous pouvez utiliser le builtin printfau lieu de l'externe seq:

printf "n%04g\n" {1..100} | xargs -n 1 -P 10 -I {} bash -c 'echo_var "$@"' _ {}

De plus, utiliser return 0et exit 0comme ça masque toute valeur d'erreur qui pourrait être produite par la commande la précédant. De plus, s'il n'y a pas d'erreur, c'est la valeur par défaut et donc quelque peu redondante.

@phobic mentionne que la commande Bash pourrait être simplifiée à

bash -c 'echo_var "{}"'

déplacer le {}directement à l'intérieur. Mais il est vulnérable à l'injection de commandes comme le souligne @Sasha.

Voici un exemple des raisons pour lesquelles vous ne devriez pas utiliser le format intégré:

$ echo '$(date)' | xargs -I {} bash -c 'echo_var "{}"'
Sun Aug 18 11:56:45 CDT 2019

Un autre exemple de pourquoi pas :

echo '\"; date\"' | xargs -I {} bash -c 'echo_var "{}"'

Voici ce qui est produit en utilisant le format sécurisé :

$ echo '$(date)' | xargs -I {} bash -c 'echo_var "$@"' _ {}
$(date)

Ceci est comparable à l'utilisation de requêtes SQL paramétrées pour éviter l' injection .

J'utilise datedans une substitution de commande ou entre guillemets échappés ici au lieu de la rmcommande utilisée dans le commentaire de Sasha car elle est non destructive.

Suspendu jusqu'à nouvel ordre.
la source
14
Un peu plus de discussion: xargs exécute une toute nouvelle instance du processus nommé. Dans ce cas, vous indiquez le nom echo_var, qui est une fonction dans ce script, et non un processus (programme) dans votre PATH. La solution de Dennis est d'exporter la fonction pour les processus bash enfants à utiliser, puis de passer au sous-processus et de s'y exécuter.
David Souther
7
quelle est la signification de _et \, sans eux, cela ne fonctionnait pas pour moi
Hashbrown
9
@Hashbrown: Le trait de soulignement ( _) fournit un espace réservé pour argv[0]( $0) et presque tout peut y être utilisé. Je pense que j'ai ajouté la barre oblique inverse-point-virgule ( \;) en raison de son utilisation pour terminer la -execclause dans find, mais cela fonctionne pour moi sans elle ici. En fait, si la fonction devait être utilisée à la $@place de $1alors, elle verrait le point-virgule comme un paramètre, il devrait donc être omis.
Suspendu jusqu'à nouvel ordre.
4
L'argument -i de xargs est depuis obsolète. Utilisez plutôt -I (i majuscule).
Nicolai S
11
Vous pouvez simplifier cela en incluant l'argument de xargs dans la chaîne de commande pour bash avec bash -c 'echo_var "{}"'. Vous n'avez donc pas besoin du _ {} à la fin.
phobique le
16

L'utilisation de GNU Parallel ressemble à ceci:

#!/bin/bash
echo_var(){
    echo $1
    return 0
}
export -f echo_var
seq -f "n%04g" 1 100 | parallel -P 10 echo_var {} 
exit 0

Si vous utilisez la version 20170822, vous n'avez même pas besoin de le faire export -ftant que vous avez exécuté ceci:

. `which env_parallel.bash`
seq -f "n%04g" 1 100 | env_parallel -P 10 echo_var {} 
Ole Tange
la source
où puis-je acheter pour osx?
Nick
nvm il est réglé dans zsh
Nick
Obtenir ceci ci-dessous eerror Ole sh: parallel_bash_environment: line 67: unexpected EOF while looking for matching '' sh: parallel_bash_environment: ligne 79: erreur de syntaxe: fin inattendue du fichier sh: erreur lors de l'importation de la définition de fonction pour parallel_bash_environment' /usr/local/bin/bash: parallel_bash_environment: line 67: unexpected EOF while looking for matching '' / usr / local / bin / bash: parallel_bash_environment: ligne 79: erreur de syntaxe: fin inattendue de file / usr / local / bin / bash: erreur lors de l'importation de la définition de la fonction pour `...
Nick
Vous avez été choqué: Shellshock n'a pas affecté directement GNU Parallel. La solution à shellshock, cependant, a fait: il a complètement cassé --env et l'astuce env_parallel. On pense qu'il est corrigé dans la version git: git.savannah.gnu.org/cgit/parallel.git/snapshot/…
Ole Tange
1
J'aime cette réponse, car elle m'a fait découvrir l'outil parallèle
JR Utily
10

Quelque chose comme ça devrait fonctionner aussi:

function testing() { sleep $1 ; }
echo {1..10} | xargs -n 1 | xargs -I@ -P4 bash -c "$(declare -f testing) ; testing @ ; echo @ "
Ermite
la source
1

C'est peut-être une mauvaise pratique, mais si vous définissez des fonctions dans un .bashrcscript ou un autre, vous pouvez envelopper le fichier ou au moins les définitions de fonction avec un paramètre de allexport:

set -o allexport

function funcy_town {
  echo 'this is a function'
}
function func_rock {
  echo 'this is a function, but different'
}
function cyber_func {
  echo 'this function does important things'
}
function the_man_from_funcle {
  echo 'not gonna lie'
}
function funcle_wiggly {
  echo 'at this point I\'m doing it for the funny names'
}
function extreme_function {
  echo 'goodbye'
}

set +o allexport
xdhmoore
la source