Ho un comando chiamato CMD dal mio script principale di Bourne, che impiega per sempre.
Voglio modificare lo script come segue:
Qualcuno può darmi dei consigli per farlo?
1: In bash, $!
contiene il PID dell'ultimo processo in background che è stato eseguito. Ciò ti dirà quale processo monitorare, comunque.
4: wait <n>
attende fino a quando il processo con ID è completo (si bloccherà fino al completamento del processo, quindi potresti non voler chiamare questo finché non sei sicuro che il processo sia completato). Dopo il ritorno di wait
, il codice di uscita del processo viene restituito nella variabile $?
2, 3: ps
o ps | grep " $! "
possono dirti se il processo è ancora in esecuzione. Spetta a te capire l'output e decidere quanto è vicino alla finitura. (ps | grep
non è a prova di idiota. Se hai tempo puoi trovare un modo più efficace per capire se il processo è ancora in esecuzione).
Ecco uno script scheletro:
# simulate a long process that will have an identifiable exit code
(sleep 15 ; /bin/false) &
my_pid=$!
while ps | grep " $my_pid " # might also need | grep -v grep here
do
echo $my_pid is still in the ps output. Must still be running.
sleep 3
done
echo Oh, it looks like the process is done.
wait $my_pid
my_status=$?
echo The exit status of the process was $my_status
Ecco come l'ho risolto quando ho avuto una necessità simile:
# Some function that takes a long time to process
longprocess() {
# Sleep up to 14 seconds
sleep $((RANDOM % 15))
# Randomly exit with 0 or 1
exit $((RANDOM % 2))
}
pids=""
# Run five concurrent processes
for i in {1..5}; do
( longprocess ) &
# store PID of process
pids+=" $!"
done
# Wait for all processes to finnish, will take max 14s
for p in $pids; do
if wait $p; then
echo "Process $p success"
else
echo "Process $p fail"
fi
done
#/bin/bash
#pgm to monitor
tail -f /var/log/messages >> /tmp/log&
# background cmd pid
pid=$!
# loop to monitor running background cmd
while :
do
ps ax | grep $pid | grep -v grep
ret=$?
if test "$ret" != "0"
then
echo "Monitored pid ended"
break
fi
sleep 5
done
wait $pid
echo $?
Come vedo quasi tutte le risposte utilizzano utility esterne (principalmente ps
) per interrogare lo stato del processo in background. C'è una soluzione più unixesh, che cattura il segnale SIGCHLD. Nel gestore del segnale deve essere controllato quale processo figlio è stato fermato. Può essere fatto da kill -0 <PID>
built-in (universale) o controllando l'esistenza della directory /proc/<PID>
(specifica per Linux) o usando la jobs
built-in ( bash specifica. jobs -l
riporta anche il pid. In questo caso il terzo campo dell'output può essere Stopped | Running | Done | Exit.).
Ecco il mio esempio.
Il processo avviato è chiamato loop.sh
. Accetta -x
o un numero come argomento. Per -x
si esce con il codice di uscita 1. Per un numero attende num * 5 secondi. In ogni 5 secondi stampa il suo PID.
Il processo di avvio si chiama launch.sh
:
#!/bin/bash
handle_chld() {
local tmp=()
for((i=0;i<${#pids[@]};++i)); do
if [ ! -d /proc/${pids[i]} ]; then
wait ${pids[i]}
echo "Stopped ${pids[i]}; exit code: $?"
else tmp+=(${pids[i]})
fi
done
pids=(${tmp[@]})
}
set -o monitor
trap "handle_chld" CHLD
# Start background processes
./loop.sh 3 &
pids+=($!)
./loop.sh 2 &
pids+=($!)
./loop.sh -x &
pids+=($!)
# Wait until all background processes are stopped
while [ ${#pids[@]} -gt 0 ]; do echo "WAITING FOR: ${pids[@]}"; sleep 2; done
echo STOPPED
Per ulteriori spiegazioni vedere: Avvio di un processo da script di bash non riuscito
Il pid di un processo figlio in background è memorizzato in $! . Puoi memorizzare tutti i pid dei processi figli in una matrice, ad es. PIDS [] .
wait [-n] [jobspec or pid …]
Attendere fino a quando il processo figlio specificato da ciascun ID di processo ID o le specifiche del processo specificate in jobspec escono e restituire lo stato di uscita dell'ultimo comando atteso. Se viene specificata una specifica del lavoro, vengono attesi tutti i processi nel lavoro. Se non vengono forniti argomenti, vengono attesi tutti i processi figli attualmente attivi e lo stato di ritorno è zero. Se viene fornita l'opzione -n, attendere attende che qualsiasi lavoro venga terminato e restituisce lo stato di uscita. Se né jobspec né pid specificano un processo figlio attivo di Shell, lo stato di ritorno è 127.
Usa wait command puoi aspettare che finiscano tutti i processi figli, nel frattempo puoi ottenere lo stato di uscita di ogni processo figlio tramite $? e memorizza lo stato in STATUS [] . Quindi puoi fare qualcosa in base allo stato.
Ho provato le seguenti 2 soluzioni e funzionano bene. solution01 is più conciso, mentre solution02 è un po 'complicato.
#!/bin/bash
# start 3 child processes concurrently, and store each pid into array PIDS[].
process=(a.sh b.sh c.sh)
for app in ${process[@]}; do
./${app} &
PIDS+=($!)
done
# wait for all processes to finish, and store each process's exit code into array STATUS[].
for pid in ${PIDS[@]}; do
echo "pid=${pid}"
wait ${pid}
STATUS+=($?)
done
# after all processed finish, check their exit codes in STATUS[].
i=0
for st in ${STATUS[@]}; do
if [[ ${st} -ne 0 ]]; then
echo "$i failed"
else
echo "$i finish"
fi
((i+=1))
done
#!/bin/bash
# start 3 child processes concurrently, and store each pid into array PIDS[].
i=0
process=(a.sh b.sh c.sh)
for app in ${process[@]}; do
./${app} &
pid=$!
PIDS[$i]=${pid}
((i+=1))
done
# wait for all processes to finish, and store each process's exit code into array STATUS[].
i=0
for pid in ${PIDS[@]}; do
echo "pid=${pid}"
wait ${pid}
STATUS[$i]=$?
((i+=1))
done
# after all processed finish, check their exit codes in STATUS[].
i=0
for st in ${STATUS[@]}; do
if [[ ${st} -ne 0 ]]; then
echo "$i failed"
else
echo "$i finish"
fi
((i+=1))
done
Vorrei cambiare leggermente il tuo approccio. Anziché controllare ogni pochi secondi se il comando è ancora attivo e segnalare un messaggio, avere un altro processo che riporta ogni secondo che il comando è ancora in esecuzione e quindi uccidere quel processo al termine del comando. Per esempio:
#!/bin/sh cmd () {sleep 5; uscita 24; } cmd & # Esegue il processo a lungo termine pid = $! # Registra il pid # Visualizza un processo che coninually segnala che il comando è ancora in esecuzione Mentre echo "$ (date): $ pid è ancora in esecuzione"; dormi 1; done & echoer = $! # Imposta una trappola per uccidere il reporter quando il processo termina trap 'kill $ echoer' 0 # Attendi che il processo finisca se aspetta $ pid; poi echo "cmd riuscito" else echo "cmd NON RIUSCITO !! (restituito $?)" fi
Il nostro team ha avuto la stessa necessità con uno script eseguito da SSH remoto che era scaduto dopo 25 minuti di inattività. Ecco una soluzione con il ciclo di monitoraggio che controlla il processo in background ogni secondo, ma stampa ogni 10 minuti per sopprimere un timeout di inattività.
long_running.sh &
pid=$!
# Wait on a background job completion. Query status every 10 minutes.
declare -i elapsed=0
# `ps -p ${pid}` works on macOS and CentOS. On both OSes `ps ${pid}` works as well.
while ps -p ${pid} >/dev/null; do
sleep 1
if ((++elapsed % 600 == 0)); then
echo "Waiting for the completion of the main script. $((elapsed / 60))m and counting ..."
fi
done
# Return the exit code of the terminated background process. This works in Bash 4.4 despite what Bash docs say:
# "If neither jobspec nor pid specifies an active child process of the Shell, the return status is 127."
wait ${pid}
Un semplice esempio, simile alle soluzioni sopra. Questo non richiede il monitoraggio di alcun output di processo. Il prossimo esempio usa tail per seguire l'output.
$ echo '#!/bin/bash' > tmp.sh
$ echo 'sleep 30; exit 5' >> tmp.sh
$ chmod +x tmp.sh
$ ./tmp.sh &
[1] 7454
$ pid=$!
$ wait $pid
[1]+ Exit 5 ./tmp.sh
$ echo $?
5
Utilizzare tail per seguire l'output del processo e uscire quando il processo è completo.
$ echo '#!/bin/bash' > tmp.sh
$ echo 'i=0; while let "$i < 10"; do sleep 5; echo "$i"; let i=$i+1; done; exit 5;' >> tmp.sh
$ chmod +x tmp.sh
$ ./tmp.sh
0
1
2
^C
$ ./tmp.sh > /tmp/tmp.log 2>&1 &
[1] 7673
$ pid=$!
$ tail -f --pid $pid /tmp/tmp.log
0
1
2
3
4
5
6
7
8
9
[1]+ Exit 5 ./tmp.sh > /tmp/tmp.log 2>&1
$ wait $pid
$ echo $?
5
Un'altra soluzione è monitorare i processi tramite il filesystem proc (più sicuro della combinazione ps/grep); quando si avvia un processo ha una cartella corrispondente in/proc/$ pid, quindi la soluzione potrebbe essere
#!/bin/bash
....
doSomething &
local pid=$!
while [ -d /proc/$pid ]; do # While directory exists, the process is running
doSomethingElse
....
else # when directory is removed from /proc, process has ended
wait $pid
local exit_status=$?
done
....
Ora puoi usare la variabile $ exit_status come preferisci.
Con questo metodo, il tuo script non deve attendere il processo in background, dovrai solo monitorare un file temporaneo per lo stato di uscita.
FUNCmyCmd() { sleep 3;return 6; };
export retFile=$(mktemp);
FUNCexecAndWait() { FUNCmyCmd;echo $? >$retFile; };
FUNCexecAndWait&
ora, il tuo script può fare qualsiasi altra cosa mentre devi solo continuare a monitorare il contenuto di retFile (può anche contenere qualsiasi altra informazione tu voglia come il tempo di uscita).
PS .: btw, ho pensato in codice in bash
Questo potrebbe estendersi oltre la tua domanda, tuttavia se sei preoccupato per il periodo di tempo in cui i processi sono in esecuzione, potresti essere interessato a controllare lo stato dei processi in background in esecuzione dopo un intervallo di tempo. È abbastanza facile controllare quali PID figli sono ancora in esecuzione usando pgrep -P $$
, tuttavia ho trovato la seguente soluzione per verificare lo stato di uscita di quei PID che sono già scaduti:
cmd1() { sleep 5; exit 24; }
cmd2() { sleep 10; exit 0; }
pids=()
cmd1 & pids+=("$!")
cmd2 & pids+=("$!")
lasttimeout=0
for timeout in 2 7 11; do
echo -n "interval-$timeout: "
sleep $((timeout-lasttimeout))
# you can only wait on a pid once
remainingpids=()
for pid in ${pids[*]}; do
if ! ps -p $pid >/dev/null ; then
wait $pid
echo -n "pid-$pid:exited($?); "
else
echo -n "pid-$pid:running; "
remainingpids+=("$pid")
fi
done
pids=( ${remainingpids[*]} )
lasttimeout=$timeout
echo
done
quali uscite:
interval-2: pid-28083:running; pid-28084:running;
interval-7: pid-28083:exited(24); pid-28084:running;
interval-11: pid-28084:exited(0);
Nota: è possibile modificare $pids
in una variabile stringa anziché in matrice per semplificare le cose, se lo si desidera.