Bash, comment laisser certains processus d'arrière-plan s'exécuter mais en attendre d'autres?

11

J'ai (encore) un autre wait, &, &&contrôle question de flux ..

Disons que j'ai un script quelque chose comme ça où je veux faire autant de travail en même temps que possible:

# may take some hours
something InputA > IrrelevantA &
something InputB > IrrelevantB &

# may take an hour
(
   somethingElse InputA > OutputA &
   somethingElse InputB > OutputB &
)&& combine OutputA OutputB > Result

...morestuff

Question 1: dans le script, combineattend-on que les deux somethingElseprocessus se terminent pendant que les deux somethingprocessus continuent?

Question 2: Sinon - et je soupçonne que ce n'est pas le cas - comment puis-je combineattendre uniquement les deux somethingElseprocessus alors que les somethingprocessus ci-dessus continuent de fonctionner en arrière-plan?

Stephen Henderson
la source

Réponses:

13

Dans votre exemple, la combinecommande sera exécutée dès que le sous-shell se terminera (et à condition que le dernier processus d'arrière-plan ait été démarré sans erreur). Le sous-shell se fermera immédiatement après le démarrage des travaux car il n'y a pas de waitcommande.

Si vous souhaitez exécuter une commande basée sur la valeur de retour de deux ou plusieurs processus d'arrière-plan simultanés, je ne vois pas d'autre moyen que d'utiliser des fichiers temporaires pour les valeurs de retour. En effet, waitne peut renvoyer que la valeur de retour de l' un des processus qu'il attend. De plus, comme les processus d'arrière-plan doivent être exécutés en sous-coquilles pour obtenir leurs valeurs de retour, ils ne peuvent pas être stockés dans des variables. Vous pourriez faire:

something InputA >IrrelevantA &
something InputB >IrrelevantB &

tmp1=$(mktemp)
tmp2=$(mktemp)

( somethingElse InputA >OutputA; echo $? >"$tmp1" ) &
proc1=$!

( somethingElse InputB >OutputB; echo $? >"$tmp2" ) &
proc2=$!

wait "$proc1" "$proc2"

read ret1 <"$tmp1"
read ret2 <"$tmp2"
[ "$ret1" = 0 && "ret2" = 0 ] && combine OutputA OutputB >Result

rm "$tmp1" "$tmp2"

Si vous ne vous souciez pas vraiment des valeurs de retour, vous pouvez simplement démarrer les travaux normalement et utiliser wait:

something InputA >IrrelevantA &
something InputB >IrrelevantB &

somethingElse InputA >OutputA &
proc1=$!

somethingElse InputB >OutputB &
proc2=$!

wait "$proc1" "$proc2"
combine OutputA OutputB >Result
Graeme
la source
Salut, je pense que la 2ème option fonctionnerait pour moi ...
Stephen Henderson
3

La substitution de processus serait-elle plus efficace, surtout si vous n'avez pas besoin d'enregistrer les fichiers OutputAet OutputB, et ne vous en souciez que Result? Serait-ce un gain de temps particulièrement important, car si vous avez une E / S lente en écriture sur le disque, l'enregistrement des fichiers OutputAet OutputBpourrait être l'étape de limitation de débit?

combine  <(somethingElse InputA)  <(somethingElse InputB)  >  Result

La substitution de processus vous permet de mettre la commande à l'intérieur <(..here..)au lieu d'enregistrer la sortie dans un fichier, puis de la lire comme entrée dans l'étape "combine".

Si la mémoire est une limitation, et la taille de outputAet outputBplus que ce que la mémoire peut contenir, va-t-elle aller à l'encontre du but?

Attendra combineque les deux processus soient terminés avant de démarrer?

TW Tan
la source
Ce n'est pas «Jeopardy»; veuillez ne pas formuler votre réponse sous forme de question. Sérieusement, vous avez trouvé une nouvelle idée, et je pense qu'elle est plutôt bonne. Pour répondre à quelques-uns de vos points: combinecommencera à s'exécuter dès que les deux somethingElsecommandes auront démarré, mais ce n'est pas grave, car les <(…)choses sont des tuyaux; combinesera donc simplement obligé d'attendre des données s'il dépasse les somethingElseprocessus. Et, comme ce sont des tuyaux, la taille n'est pas un problème. … (Suite)
G-Man dit «Réintègre Monica»
(Suite)… Le seul problème de fond que j'ai avec votre réponse est qu'elle ne permet pas de tester l'état de sortie des somethingElseprocessus - et il n'est pas tout à fait clair si c'est important pour le demandeur. Mais, aussi, une réponse ne devrait pas être de poser des questions comme ça.
G-Man dit `` Réintègre Monica ''
2

Vous pouvez utiliser la waitcommande:

(echo starting & sleep 10 & wait) && echo done

Vous pouvez voir la ligne de "départ" se produire tout de suite, et le "fait" attend 10 secondes.

psusi
la source
l'attente requiert généralement des processus enfants du même shell. Attendez, c'est assez délicat là-bas.
mikeserv
1
@mikeserv, de quoi tu parles? Voilà le point: il attend tous les enfants de ce sous-shell.
psusi
par mes premiers tests, cela fonctionne. Je vais l'essayer sur le gros script maintenant
Stephen Henderson
Exactement - enfants du même obus - sous- obus. Cela devrait fonctionner pour tout processus qui n'essaie pas de s'échapper - ou démonize ou autre chose. C'est tout ce que je voulais dire - tant que vos processus respectent les chefs de processus, l'attente est ok, mais dès qu'un processus tente de devenir son propre chef de processus, l'attente aura des problèmes.
mikeserv
0

En fait, je montre exactement comment ce genre de chose pourrait être fait dans une autre réponse ici . Cette réponse était à une question sur la garantie que 2 journaux étaient maintenus par un processus d'arrière-plan, donc je l'ai démontré avec 10.

Script de démonstration

cat <<-\DEMO >|${s=/tmp/script} 
printf 'tty is %s\nparent pid is %s\npid is pid=%s\n' \
     "$(tty)" "$PPID" "$$"
exec 1>&2 ; nums=$(seq 0 9)
rm ${files=$(printf "/tmp/file%s\n" $nums)}
for n in $nums ; do { for f in $files ; do
    echo "Line $n" >>"$f" ; done
sleep 1 ; } ; done
#END
DEMO

Lancer la démo

s=/tmp/script ;chmod +x $s ;info="$(($s &)2>&- &)"
echo "$info" ; pid="${info##*=}" ; echo
while ps -p $pid >/dev/null ; do sleep 3 ; done
for f in /tmp/file[0-9] ; do
    printf 'path : %s\tline count : %s\n' \
        $f $(<$f wc -l)
done

Production:

tty is not a tty
parent pid is 1
pid is 12123

path : /tmp/file0    line count : 10
path : /tmp/file1    line count : 10
path : /tmp/file2    line count : 10
path : /tmp/file3    line count : 10
path : /tmp/file4    line count : 10
path : /tmp/file5    line count : 10
path : /tmp/file6    line count : 10
path : /tmp/file7    line count : 10
path : /tmp/file8    line count : 10
path : /tmp/file9    line count : 10

Ce qui précède le démontre. Il construit et exécute un script nommé /tmp/script, chmod« est comme exécutable, et il exécute en &backgroundun &backgrounded ( subshell ).

Le script rms /tmp/file0-910 fichiers et echoesune ligne chaque seconde dans chacun des 10 d'entre eux. J'en capture une partie $infodu processus désavoué et je la présente via des $(command substitution). While psrapports fixes sur la $pidcapture I, je sais qu'elle s'exécute toujours donc I sleep.Une fois terminée, les lignes des 10 fichiers sont comptées avecwc.

Après avoir invoqué un processus de cette manière, vous pouvez fermer librement son processus parent d'origine et il continuera à se déplacer - il est effectivement renié. Cela signifie également que vous ne pouvez pas utiliser la classique waitcommande, mais en attente sur psdes » retour devrait être plus robuste dans tous les cas.

Il convient de mentionner, je pense, que le processus est en fait initialement appelé $(command substitution)et printfsque $infoje souhaite que je puisse le contrôler efficacement. Mais dès qu'il laisse tomber sa sortie de terminal avec exec 1>&2(qui est fermée dans le même sous-shell avec 2>&-), le processus s'échappe et je dois l'attendre à l'autre bout. Un peu le meilleur des deux mondes, surtout si vous l'utilisez pour gérer les canaux d'entrée, tant que vous pouvez vous concentrer sur toutes les redirections et les leaders de processus.

Tout le reste est juste pour démonstration ici. Tout ce dont vous avez besoin pour exécuter ceci est le meilleur script et:

info="$(($script_path &)2>&- &)"    

REMARQUE: cela imprime uniquement au terminal exactement ce que je voulais démontrer. Comme indiqué par le$PPID,processus, ce terminal est renié par le terminal et est un enfant direct de$PID 1.

Si vous vouliez exécuter deux d'entre eux en même temps et les attendre, vous pouvez simplement remettre leurs psdeux pids et attendre.

mikeserv
la source