risposta-alla-domanda-sullo-sviluppo-web-bd.com

Ottieni il codice di uscita di un processo in background

Ho un comando chiamato CMD dal mio script principale di Bourne, che impiega per sempre.

Voglio modificare lo script come segue:

  1. Esegui il comando CMD in parallelo come processo in background ($ CMD e).
  2. Nello script principale, avere un ciclo per monitorare il comando generato ogni pochi secondi. Il ciclo fa anche eco ad alcuni messaggi allo stdout che indicano lo stato di avanzamento dello script.
  3. Esci dal ciclo quando termina il comando generato.
  4. Cattura e segnala il codice di uscita del processo generato.

Qualcuno può darmi dei consigli per farlo?

102
bob

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
104
mob

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
40
Bjorn
#/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 $?
7
Abu Aqil

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

7
TrueY

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.

solution01

#!/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

solution02

#!/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
5
Terry wang

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 
4
William Pursell

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}
3
DKroot

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
2
Darren Weber

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

0
Aquarius Power

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. 

0
errant.info