Bash, come far funzionare alcuni processi in background ma aspettare altri?


11

Ho (ancora) un altro wait, &, &&questione flusso di controllo ..

Supponiamo che io abbia una sceneggiatura simile a questa in cui voglio fare il maggior lavoro possibile allo stesso tempo:

# 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

Domanda 1: Nello script, combineattende il completamento di entrambi i somethingElseprocessi mentre entrambi somethingcontinuano?

Domanda 2: In caso contrario - e sospetto che non lo sia - come posso combineaspettare solo per entrambi i somethingElseprocessi mentre i somethingprocessi sopra continuano a funzionare in background?

Risposte:


13

Nel tuo esempio il combinecomando verrà eseguito non appena esce la subshell (e purché l'ultimo processo in background sia stato avviato senza errori). La subshell uscirà immediatamente dopo l'avvio dei lavori poiché non è presente alcun waitcomando.

Se si desidera eseguire un comando basato sul valore restituito di due o più processi in background simultanei, non posso vedere altro che utilizzare file temporanei per i valori restituiti. Questo perché waitpuò restituire solo il valore di ritorno di uno dei processi che attende. Inoltre, poiché i processi in background devono essere eseguiti in subshells per ottenere i loro valori di ritorno, non possono essere memorizzati in variabili. Potresti fare:

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"

Se non ti interessano davvero i valori di ritorno, puoi semplicemente avviare i lavori normalmente e utilizzare wait:

something InputA >IrrelevantA &
something InputB >IrrelevantB &

somethingElse InputA >OutputA &
proc1=$!

somethingElse InputB >OutputB &
proc2=$!

wait "$proc1" "$proc2"
combine OutputA OutputB >Result

Ciao, penso che la seconda opzione funzionerebbe per me ...
Stephen Henderson,

3

Sostituzione di processo sarebbe più efficace, soprattutto se non c'è bisogno di salvare i file OutputAe OutputB, e interessa solo Result? Ciò comporterebbe un notevole risparmio di tempo perché se si dispone di un I / O lento nella scrittura su disco, il salvataggio dei file OutputAe OutputBpotrebbe essere il passaggio di limitazione della velocità?

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

La sostituzione del processo consente di inserire il comando <(..here..)invece di salvare l'output in un file e quindi di leggerlo come input nel passaggio "combina".

Se la memoria è una limitazione, e la dimensione di outputAe outputBpiù di ciò che la memoria può contenere, sconfiggerà l'intero scopo?

Sarà combineaspettare fino a entrambi i processi sono stati completati prima del suo inizio in esecuzione?


Questa non è "Jeopardy"; per favore non esprimere la tua risposta sotto forma di domanda. Seriamente, hai avuto una nuova idea e penso che sia abbastanza buono. Per rispondere a un paio di punti: combineinizierà a funzionare non appena i due somethingElsecomandi sono stati avviati, ma va bene, perché le <(…)cose sono pipe; quindi combinesarà semplicemente costretto ad attendere i dati se supera i somethingElseprocessi. E, poiché sono pipe, le dimensioni non sono un problema. ... (proseguendo)
G-Man dice "Ripristina Monica" il

(Proseguendo) ... L'unico problema sostanziale che ho con la tua risposta è che non consente di testare lo stato di uscita dei somethingElseprocessi - e non è del tutto chiaro se questo è importante per chi chiede. Ma anche una risposta non dovrebbe essere fare domande del genere.
G-Man dice "Ripristina Monica" il

2

Puoi usare il waitcomando:

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

Puoi vedere subito la linea "iniziale" e il "fatto" attende 10 secondi.


generalmente aspettare richiede processi figlio della stessa shell. Aspetta è piuttosto complicato lì.
Mikeserv,

1
@mikeserv, di cosa stai parlando? Questo è il punto: aspetta tutti i bambini in quella subshell.
psusi

dai miei test iniziali questo funziona. Ora lo proverò sul grande copione
Stephen Henderson,

Esattamente - i bambini della stessa shell - sub shell. Dovrebbe funzionare per qualsiasi processo che non tenti di scappare - o daemonize o altro. Questo è tutto ciò che intendevo dire: finché i processi rispettano i leader del processo, l'attesa è ok, ma non appena un processo tenta di diventare il proprio leader del processo, l'attesa avrà dei problemi.
Mikeserv,

0

In realtà, dimostrerò esattamente come questo genere di cose potrebbe essere fatto in un'altra risposta qui . Quella risposta era una domanda su come garantire che 2 log fossero gestiti da un processo in background, quindi l'ho dimostrato con 10.

Demo Script

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

Esegui Demo

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

Produzione:

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

Quanto sopra dimostra. Si costruisce e esegue uno script chiamato /tmp/script, chmod'il come eseguibile, e lo esegue in &backgrounduna &backgrounded ( subshell ).

Lo script rms /tmp/file0-910 file e echoesuna riga ogni secondo in tutti e 10. Ne acquisisco alcuni $infodal processo sconosciuto e lo presento tramite $(command substitution). While psreport ancora $pidsull'acquisizione, so che funziona ancora così I sleep.Quando viene completato, le righe in tutti i 10 file vengono conteggiate conwc.

Dopo aver invocato un processo in questo modo, puoi chiudere liberamente il suo processo genitore originale e continuerà a trasportare camion: è effettivamente rinnegato. Questo significa anche che non è possibile utilizzare il tradizionale waitcomando, ma in attesa di ps's ritorno dovrebbe essere più robusto, in ogni caso.

Vale la pena ricordare che penso che il processo sia stato inizialmente chiamato $(command substitution)e printfsme lo $infovoglio, così posso controllarlo efficacemente. Ma non appena rilascia l'output del terminale con exec 1>&2(che è chiuso nella stessa subshell con 2>&-), il processo sfugge e devo aspettare all'altra estremità. Kinda il meglio di entrambi i mondi, specialmente se lo usi per gestire i tubi di input, a patto che tu possa avvolgere la tua mente intorno a tutti i reindirizzamenti e ai leader di processo.

Tutto il resto è solo per dimostrazione qui. Tutto ciò che serve per eseguire questo è lo script principale e:

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

NOTA: Questo stampa solo sul terminale esattamente ciò che desideravo dimostrarlo. Come notato da$PPID,questo processo è rinnegato dal terminale ed è figlio diretto di$PID 1.

Se volessi eseguire due di questi contemporaneamente e aspettarli, potresti semplicemente consegnare psentrambi i loro pid e aspettare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.