2009-10-15 15 views
86

Tengo un comando CMD llamado desde mi script principal de bourne shell que lleva una eternidad.shell - obtener el código de salida del proceso en segundo plano

Quiero modificar la secuencia de comandos de la siguiente manera:

  1. Run el comando cmd en paralelo como un proceso de fondo ($ CMD &).
  2. En la secuencia de comandos principal, tenga un bucle para supervisar el comando generado cada pocos segundos. El ciclo también hace eco de algunos mensajes para indicar el progreso del script.
  3. Salga del bucle cuando finaliza el comando generado.
  4. Capture e informe el código de salida del proceso generado.

¿Alguien me puede dar consejos para lograr esto?

+1

... y el ganador es? – TrueY

Respuesta

7
#/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 $? 
+2

Aquí hay un truco para evitar el 'grep -v'. Puede limitar la búsqueda al comienzo de la línea: 'grep '^' $ pid' Plus, puede hacer' ps p $ pid -o pid = ', de todos modos. Además, 'tail -f' no va a terminar hasta que lo mates, así que no creo que sea una muy buena forma de probar esto (al menos sin señalar eso). Es posible que desee redirigir la salida de su comando 'ps' a'/dev/null' o irá a la pantalla en cada iteración. Su 'exit' hace que se salte el' wait', probablemente debería ser un 'break'. Pero, ¿no son redundantes los 'while' /' ps' y 'wait'? –

+5

¿Por qué todos olvidan 'kill -0 $ pid'? En realidad, no envía ninguna señal, solo comprueba que el proceso esté activo, utilizando un shell incorporado en lugar de procesos externos. – ephemient

+1

Porque solo puede matar un proceso de su propiedad: 'bash: kill: (1) - Operación no permitida' –

89

1: En bash, $! sostiene el PID del último proceso de fondo que fue ejecutado. Eso te dirá qué proceso supervisar, de todos modos.

4: wait <n> espera hasta que se complete el proceso con ID (se bloqueará hasta que el proceso finalice, por lo que es posible que no desee llamar hasta que esté seguro de que el proceso está hecho). Después de wait devoluciones, el código de salida del proceso se devuelve en la variable $?

2, 3: ps o ps | grep " $! " le puede decir si el proceso todavía está en marcha. Depende de usted cómo entender la salida y decidir qué tan cerca está de terminar. (ps | grep no es a prueba de idiotas. Si tiene tiempo, puede encontrar una manera más robusta de saber si el proceso aún se está ejecutando).

Aquí es un script esqueleto:

# 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 
+10

'ps -p $ mi_pid -o pid =' ni 'grep' es necesario. –

+1

@Dennis Williamson 'ps' tiene muchos sabores. Tu llamada no funciona para mí, pero 'ps -p $ my_pid' sí. Su punto más grande que 'grep' no es necesario es correcto. – mob

+0

Hmmm ... en realidad no puedo encontrar una buena manera de evitar grep en Cygwin. 'ps -p $ pid' siempre tiene el estado de salida 0 si $ pid existe o no. Podría decir algo como 'while ['ps -p $ pid | wc -l '\> 1] 'pero eso no es una mejora ... – mob

4

me iba a cambiar ligeramente su enfoque. En lugar de verificar cada pocos segundos si el comando todavía está activo y de informar un mensaje, tenga otro proceso que informe cada pocos segundos que el comando aún se está ejecutando y luego elimine ese proceso cuando el comando finalice. Por ejemplo:

 
#!/bin/sh 

cmd() { sleep 5; exit 24; } 

cmd & # Run the long running process 
pid=$! # Record the pid 

# Spawn a process that coninually reports that the command is still running 
while echo "$(date): $pid is still running"; do sleep 1; done & 
echoer=$! 

# Set a trap to kill the reporter when the process finishes 
trap 'kill $echoer' 0 

# Wait for the process to finish 
if wait $pid; then 
    echo "cmd succeeded" 
else 
    echo "cmd FAILED!! (returned $?)" 
fi 
2

Un ejemplo simple, similar a las soluciones anteriores. Esto no requiere monitorear ninguna salida del proceso. El siguiente ejemplo usa cola para seguir la salida.

$ 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 

Utilice la cola para seguir la salida del proceso y salga cuando finalice el proceso.

$ 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 
0

Esto puede ser aún mayor, pues su pregunta, sin embargo, si usted está preocupado por la cantidad de tiempo para procesos se están ejecutando, puede estar interesado en la comprobación del estado de la ejecución de procesos en segundo plano después de un intervalo de tiempo.Es bastante fácil de comprobar, que los PID niño todavía se están ejecutando usando pgrep -P $$, sin embargo se me ocurrió la solución siguiente para comprobar el estado de salida de los PID que ya han caducado:

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 

que emite:

interval-2: pid-28083:running; pid-28084:running; 
interval-7: pid-28083:exited(24); pid-28084:running; 
interval-11: pid-28084:exited(0); 

Nota: Puede cambiar $pids a una variable de cadena en lugar de una matriz para simplificar las cosas si lo desea.

1

Otra solución es supervisar procesos a través del sistema de archivos proc (más seguro que el combo ps/grep); cuando se inicia un proceso que tiene una carpeta correspondiente en/proc/$ pid, por lo que la solución podría ser

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

Ahora puede utilizar la variable $ exit_status como usted quiera.

+0

¿No funciona en bash? 'Error de sintaxis:" else "inesperado (esperando" hecho ")' – benjaoming

6

Como veo casi todas las respuestas, utilizo utilidades externas (principalmente ps) para sondear el estado del proceso en segundo plano. Hay una solución más simple, capturando la señal SIGCHLD. En el manejador de señales, debe verificarse qué proceso secundario se detuvo. Se puede hacer por kill -0 <PID> incorporado (universal) o verificar la existencia del directorio /proc/<PID> (Linux específico) o usar el jobs incorporado ( específico. también informa el pid. En este caso, el tercer campo de la salida puede ser detenido | Correr | Hecho | Salir).

Aquí está mi ejemplo.

El proceso iniciado se llama loop.sh. Acepta -x o un número como argumento. Para -x sale con el código de salida 1. Para un número, espera num * 5 segundos. Cada 5 segundos imprime su PID.

El proceso lanzador se llama 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 

Para una explicación más detallada ver: Starting a process from bash script failed

+0

Dado que estamos hablando de Bash, el bucle for podría escribirse como: 'for i in $ {! Pids [@]};' utilizando la expansión de parámetros. – PlasmaBinturong

31

Así es como yo lo resolvió cuando tenía una necesidad similar:

# 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 
+0

Me gusta este enfoque. –

+0

¡Gracias! Este me parece el enfoque más simple. –

+0

¡Una forma muy agradable de resolver el problema! –

0

Con esta método, su secuencia de comandos no tiene que esperar el proceso en segundo plano, solo tendrá que supervisar un archivo temporal para el estado de salida.

FUNCmyCmd() { sleep 3;return 6; }; 

export retFile=$(mktemp); 
FUNCexecAndWait() { FUNCmyCmd;echo $? >$retFile; }; 
FUNCexecAndWait& 

ahora, la secuencia de comandos puede hacer otra cosa, mientras que sólo hay que mantener el seguimiento de los contenidos de retFile (también puede contener cualquier otra información que desee al igual que el tiempo de salida).

PS .: Por cierto, me codificado pensando en bash

2

el PID de un proceso hijo en segundo plano se almacena en $!. Puede almacenar todos los pids de procesos secundarios en una matriz, p. PIDS [].

wait [-n] [jobspec or pid …] 

Esperar a que el proceso hijo especificado por cada proceso de identificación PID o el trabajo de especificación espectrab sale y devolver el estado de salida del último comando esperó.Si se proporciona una especificación de trabajo, se esperan todos los procesos en el trabajo. Si no se proporcionan argumentos, se esperan todos los procesos hijo actualmente activos y el estado de retorno es cero. Si se proporciona la opción -n, espera a que finalice cualquier trabajo y devuelve su estado de salida. Si ninguno espectrab ni PID especifica un proceso hijo activo de la cáscara, el estado de salida es 127.

Uso esperar comando puede esperar a que todos los procesos hijo acabado, por su parte puede obtener el código de salida de cada uno de los procesos secundarios y tienda estado en ESTADO []. Entonces puedes hacer algo dependiendo del estado.

He intentado el siguiente código y funciona bien.

#!/bin/bash 

# start 3 child processes concurrently, and store each pid into 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 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 "failed" 
    else 
    echo "finish" 
    fi 
    ((i+=1)) 
done 
+0

He intentado y probé que funciona bien. Puedes leer mi explicación en código. –

+0

Lea "[¿Cómo escribo una buena respuesta?] (Https://stackoverflow.com/help/how-to-answer)" donde encontrará la siguiente información: ** ... intente mencionar cualquier limitaciones, suposiciones o simplificaciones en su respuesta. La brevedad es aceptable, pero las explicaciones más completas son mejores. ** Por lo tanto, su respuesta es aceptable, pero tiene muchas más posibilidades de obtener votos ascendentes si puede explicar el problema y su solución. :-) –

1

Nuestro equipo tenía la misma necesidad con una secuencia de comandos SSH-ejecutada a distancia que fue el tiempo de espera después de 25 minutos de inactividad. Aquí hay una solución con el ciclo de monitorización comprobando el proceso en segundo plano cada segundo, pero imprimiendo solo cada 10 minutos para suprimir un tiempo de inactividad.

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} 
Cuestiones relacionadas