Attendez que les tâches de fond de bash soient terminées dans le script

Pour maximiser l’utilisation du processeur (je lance des choses sur Debian Lenny dans EC2), j’ai un script simple pour lancer des jobs en parallèle:

#!/bin/bash for i in apache-200901*.log; do echo "Processing $i ..."; do_something_important; done & for i in apache-200902*.log; do echo "Processing $i ..."; do_something_important; done & for i in apache-200903*.log; do echo "Processing $i ..."; do_something_important; done & for i in apache-200904*.log; do echo "Processing $i ..."; do_something_important; done & ... 

Je suis assez satisfait de cette solution de travail, mais je ne pouvais pas comprendre comment écrire un code supplémentaire qui ne serait exécuté qu’une fois toutes les boucles terminées.

Y a-t-il un moyen d’en prendre le contrôle?

Il y a une commande intégrée bash pour cela.

 wait [n ...] Wait for each specified process and return its termination sta‐ tus. Each n may be a process ID or a job specification; if a job spec is given, all processes in that job's pipeline are waited for. If n is not given, all currently active child pro‐ cesses are waited for, and the return status is zero. If n specifies a non-existent process or job, the return status is 127. Otherwise, the return status is the exit status of the last process or job waited for. 

Utiliser GNU Parallel rendra votre script encore plus court et peut-être plus efficace:

 parallel 'echo "Processing "{}" ..."; do_something_important {}' ::: apache-*.log 

Cela exécutera un travail par cœur de processeur et continuera à le faire jusqu’à ce que tous les fichiers soient traités.

Votre solution divisera les tâches en groupes avant de les exécuter. Voici 32 emplois en 4 groupes:

Programmation simple

Au lieu de cela, GNU Parallel crée un nouveau processus à la fin – gardant les processeurs actifs et économisant ainsi du temps:

Planification parallèle GNU

Pour apprendre plus:

Je devais le faire récemment et me suis retrouvé avec la solution suivante:

 while true; do wait -n || { code="$?" ([[ $code = "127" ]] && exit 0 || exit "$code") break } done; 

Voici comment cela fonctionne:

wait -n ferme dès que l’une des tâches (potentiellement multiples) d’arrière-plan se termine. Il est toujours évalué à true et la boucle continue jusqu’à ce que:

  1. Code de sortie 127 : le dernier job d’arrière-plan terminé avec succès. Dans ce cas, nous ignorons le code de sortie et quittons le sous-shell avec le code 0.
  2. N’importe quel travail d’arrière-plan a échoué. Nous sortons simplement du sous-shell avec ce code de sortie.

Avec set -e , cela garantira que le script se terminera tôt et passera par le code de sortie de tout job d’arrière-plan ayant échoué.

Ceci est ma solution brute:

 function run_task { cmd=$1 output=$2 concurency=$3 if [ -f ${output}.done ]; then # experiment already run echo "Command already run: $cmd. Found output $output" return fi count=`jobs -p | wc -l` echo "New active task #$count: $cmd > $output" $cmd > $output && touch $output.done & stop=$(($count >= $concurency)) while [ $stop -eq 1 ]; do echo "Waiting for $count worker threads..." sleep 1 count=`jobs -p | wc -l` stop=$(($count > $concurency)) done } 

L’idée est d’utiliser des “jobs” pour voir combien d’enfants sont actifs en arrière-plan et attendre que ce nombre diminue (un enfant quitte). Une fois qu’un enfant existe, la tâche suivante peut être démarrée.

Comme vous pouvez le voir, il y a aussi un peu de logique supplémentaire pour éviter de lancer les mêmes expériences / commandes plusieurs fois. Il fait le travail pour moi. Cependant, cette logique peut être ignorée ou améliorée (par exemple, vérifier les horodatages de création de fichiers, les parameters d’entrée, etc.).