46 votes

bash : lancer plusieurs commandes enchaînées en arrière-plan

J'essaie d'exécuter quelques commandes dans paralel, en arrière-plan, en utilisant bash. Voici ce que j'essaie de faire :

forloop {
  //this part is actually written in perl
  //call command sequence
  print `touch .file1.lock; cp bigfile1 /destination; rm .file1.lock;`;
}

La partie entre les guillemets (``) crée un nouveau shell et exécute les commandes à la suite. Le problème est que le contrôle du programme d'origine ne revient qu'après l'exécution de la dernière commande. Je voudrais exécuter l'ensemble de la déclaration en arrière-plan (je n'attends aucune valeur de sortie/retour) et je voudrais que la boucle continue de fonctionner.

Le programme appelant (celui qui a la boucle) ne se terminera pas tant que tous les obus engendrés ne seront pas terminés.

Je pourrais utiliser des threads en perl pour créer des threads différents qui appellent des shells différents, mais cela semble excessif...

Puis-je lancer un shell, lui donner un ensemble de commandes et lui dire de passer en arrière-plan ?

32voto

Hugh Allen Points 3799

Je ne l'ai pas testé mais que diriez-vous de

print `(touch .file1.lock; cp bigfile1 /destination; rm .file1.lock;) &`;

Les parenthèses signifient exécuter dans un sous-shell mais cela ne devrait pas faire de mal.

5 votes

Vous ne voulez pas l'impression ou les backticks - même si ces commandes donnent des résultats utiles, les exécuter en arrière-plan signifie que les backticks ne les verront pas.

2 votes

(Je n'aime pas les backticks de toute façon, je trouve $() beaucoup plus facile à lire, mais ce n'est pas pertinent ici)

4 votes

Merci beaucoup ! Cela m'a aussi aidé pour un autre problème, avec bash script, où un appel à un processus externe continuait d'imprimer la sortie dans stdout même si j'utilisais " &> /dev/null", mais résolu en appelant de cette façon : `(./call_the_proccess &> /dev/null) &`.

21voto

Mad_Ady Points 220

Merci Hugh, ça a marché :

adrianp@frost:~$ (echo "started"; sleep 15; echo "stopped")
started
stopped
adrianp@frost:~$ (echo "started"; sleep 15; echo "stopped") &
started
[1] 7101
adrianp@frost:~$ stopped

[1]+  Done                    ( echo "started"; sleep 15; echo "stopped" )
adrianp@frost:~$ 

Les autres idées ne fonctionnent pas car elles lancent chaque commande en arrière-plan, et non la séquence de commandes (ce qui est important dans mon cas !).

Merci encore !

13 votes

Ne serait pas sleep 3 vous faire gagner un peu de temps dans le débogage ?

19voto

lechup Points 549

Une autre méthode consiste à utiliser la syntaxe suivante :

{ command1; command2; command3; } &
wait

Notez que le & va à la fin du groupe de commandes, et non après chaque commande. Le point-virgule après la dernière commande est nécessaire, tout comme l'espace après la première parenthèse et avant la dernière parenthèse. Le site wait à la fin garantit que le processus parent n'est pas tué avant que le processus enfant engendré (le groupe de commande) ne se termine.

Vous pouvez aussi faire des trucs fantaisistes comme rediriger stderr et stdout :

{ command1; command2; command3; } 2>&2 1>&1 &

Votre exemple serait le suivant :

forloop() {
    { touch .file1.lock; cp bigfile1 /destination; rm .file1.lock; } &
}
# ... do some other concurrent stuff
wait # wait for childs to end

14voto

GavinCattell Points 2435
for command in $commands
do
    "$command" &
done
wait

L'esperluette à la fin de la commande l'exécute en arrière-plan, et le symbole wait attend que la tâche d'arrière-plan soit terminée.

7 votes

Ce n'est pas bon car chaque commande s'exécute en arrière-plan, pas la séquence de commandes.

4 votes

Si les commandes contiennent des espaces, cela ne fonctionne pas, elles le font généralement.

5voto

NVRAM Points 2555

GavinCattell est le plus proche (pour bash, IMO), mais comme Mad_Ady l'a souligné, il ne gère pas les fichiers "lock". Ceci devrait :

S'il y a d'autres travaux en attente, le attendre Je les attendrai aussi. Si vous devez attendre uniquement les copies, vous pouvez accumuler ces PIDs et n'attendre que ceux-là. Sinon, vous pourriez supprimer les 3 lignes avec "pids" mais c'est plus général.

En outre, j'ai ajouté une vérification pour éviter complètement la copie :

pids=
for file in bigfile*
do
    # Skip if file is not newer...
    targ=/destination/$(basename "${file}")
    [ "$targ" -nt "$file" ] && continue

    # Use a lock file:  ".fileN.lock" for each "bigfileN"
    lock=".${file##*/big}.lock"
    ( touch $lock; cp "$file" "$targ"; rm $lock ) &
    pids="$pids $!"
done
wait $pids

Par ailleurs, il semble que vous copiez de nouveaux fichiers vers un dépôt FTP (ou similaire). Si c'est le cas, vous pourrait envisager une stratégie de copie/renommage au lieu des fichiers de verrouillage (mais c'est un autre sujet).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X