Tengo un comando CMD llamado desde mi script de shell bourne principal que tarda una eternidad.
Quiero modificar el script de la siguiente manera:
- Ejecute el comando CMD en paralelo como un proceso en segundo plano (
CMD &
). - En el script principal, tenga un bucle para monitorear el comando generado cada pocos segundos. El bucle también hace eco de algunos mensajes en stdout que indican el progreso del script.
- Salga del bucle cuando finalice el comando generado.
- Capture e informe el código de salida del proceso generado.
¿Alguien puede darme consejos para lograr esto?
Respuestas:
1: en bash,
$!
contiene el PID del último proceso en segundo plano que se ejecutó. De todos modos, eso te dirá qué proceso monitorear.4:
wait <n>
espera hasta que<n>
se complete el proceso con PID (se bloqueará hasta que se complete el proceso, por lo que es posible que no desee llamar a esto hasta que esté seguro de que el proceso ha finalizado), y luego devuelve el código de salida del proceso completado.2, 3:
ps
ops | grep " $! "
puede decirle si el proceso aún se está ejecutando. Depende de usted cómo entender el resultado y decidir qué tan cerca está de terminar. (ps | grep
no es a prueba de idiotas. Si tiene tiempo, puede encontrar una forma más sólida de saber si el proceso aún se está ejecutando).Aquí hay un script esqueleto:
fuente
ps -p $my_pid -o pid=
ningunogrep
es necesario.kill -0 $!
es una mejor manera de saber si un proceso aún se está ejecutando. En realidad, no envía ninguna señal, solo verifica que el proceso esté activo, utilizando un shell incorporado en lugar de procesos externos. Comoman 2 kill
dice, "Si sig es 0, entonces no se envía ninguna señal, pero aún se realiza la verificación de errores; esto se puede usar para verificar la existencia de una ID de proceso o ID de grupo de proceso".kill -0
devolverá un valor distinto de cero si no tiene permiso para enviar señales a un proceso que se está ejecutando. Desafortunadamente, regresa1
tanto en este caso como en el caso donde el proceso no existe. Esto lo hace útil a menos que no sea el propietario del proceso, que puede ser el caso incluso para los procesos que creó si una herramienta como estasudo
está involucrada o si son setuid (y posiblemente eliminen privs).wait
no devuelve el código de salida en la variable$?
. Simplemente devuelve el código de salida y$?
es el código de salida del último programa en primer plano.kill -0
. Aquí hay una referencia revisada por pares de SO que muestra que el comentario de CraigRinger es legítimo con respecto a:kill -0
devolverá un valor distinto de cero para los procesos en ejecución ... perops -p
siempre devolverá 0 para cualquier proceso en ejecución .Así es como lo resolví cuando tenía una necesidad similar:
fuente
wait
s hace que el script espere hasta el final de (cada) proceso.El pid de un proceso secundario en segundo plano se almacena en $! . Puede almacenar todos los pids de los procesos secundarios en una matriz, por ejemplo, PIDS [] .
Espere hasta que el proceso secundario especificado por cada ID de proceso pid o especificación de trabajo se cierre y regrese el estado de salida del último comando esperado. 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 secundarios actualmente activos y el estado de retorno es cero. Si se proporciona la opción -n, esperar espera a que finalice cualquier trabajo y devuelve su estado de salida. Si ni jobspec ni pid especifican un proceso secundario activo del shell, el estado de retorno es 127.
Utilice el comando wait , puede esperar a que finalicen todos los procesos secundarios, mientras que puede obtener el estado de salida de cada proceso secundario a través de $? y almacenar el estado en ESTADO [] . Entonces puedes hacer algo dependiendo del estado.
He intentado las siguientes 2 soluciones y funcionan bien. solution01 es más conciso, mientras que solution02 es un poco complicado.
solución01
solución02
fuente
pid=$!; PIDS[$i]=${pid}; ((i+=1))
se puede escribir de manera más simple, yaPIDS+=($!)
que simplemente se agrega a la matriz sin tener que usar una variable separada para la indexación o el propio pid. Lo mismo se aplica a laSTATUS
matriz.Como veo, casi todas las respuestas usan utilidades externas (principalmente
ps
) para sondear el estado del proceso en segundo plano. Hay una solución más unixesh, capturando la señal SIGCHLD. En el controlador de señal se debe verificar qué proceso secundario se detuvo. Se puede hacerkill -0 <PID>
incorporando (universal) o comprobando la existencia de un/proc/<PID>
directorio (específico de Linux) o utilizando eljobs
incorporado (intentoespecífico.jobs -l
También informa el pid. En este caso, el tercer campo de la salida se puede Detener | En ejecución | Listo | Salir. )Aquí está mi ejemplo.
Se llama al proceso lanzado
loop.sh
. Acepta-x
o un número como argumento. For-x
es salidas con código de salida 1. Para un número espera num * 5 segundos. En cada 5 segundos imprime su PID.El proceso del iniciador se llama
launch.sh
:Para obtener más explicaciones, consulte: Error al iniciar un proceso desde el script bash
fuente
for i in ${!pids[@]};
usando la expansión de parámetros.fuente
grep -v
. Puede limitar la búsqueda al comienzo de la línea:grep '^'$pid
además, puede hacerlops 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 manera de demostrar esto (al menos sin señalarlo). Es posible que desee redirigir la salida de sups
comando/dev/null
o irá a la pantalla en cada iteración. Susexit
causaswait
se omiten, probablemente debería ser abreak
. ¿Pero no son loswhile
/ps
y loswait
redundantes?kill -0 $pid
? En realidad, no envía ninguna señal, solo verifica que el proceso esté activo, utilizando un shell incorporado en lugar de procesos externos.bash: kill: (1) - Operation not permitted
Cambiaría tu enfoque ligeramente. En lugar de verificar cada pocos segundos si el comando aún está vivo e informar un mensaje, haga otro proceso que informe cada pocos segundos que el comando aún se está ejecutando y luego elimine ese proceso cuando finalice el comando. Por ejemplo:
fuente
while kill -0 $pid 2> /dev/null; do X; done
, espero que sea útil para alguien más en el futuro que lea este mensaje;)Nuestro equipo tenía la misma necesidad con un script remoto ejecutado por SSH que estaba agotando el tiempo de espera después de 25 minutos de inactividad. Aquí hay una solución con el ciclo de monitoreo que verifica el proceso en segundo plano cada segundo, pero imprime solo cada 10 minutos para suprimir un tiempo de inactividad.
fuente
Un ejemplo simple, similar a las soluciones anteriores. Esto no requiere monitorear ningún resultado del proceso. El siguiente ejemplo usa tail para seguir la salida.
Use tail para seguir la salida del proceso y salga cuando el proceso se haya completado.
fuente
Otra solución es monitorear procesos a través del sistema de archivos proc (más seguro que el combo ps / grep); cuando inicia un proceso, tiene una carpeta correspondiente en / proc / $ pid, por lo que la solución podría ser
Ahora puede usar la variable $ exit_status como quiera.
fuente
Syntax error: "else" unexpected (expecting "done")
Con este método, su script no tiene que esperar el proceso en segundo plano, solo tendrá que monitorear un archivo temporal para ver el estado de salida.
ahora, su script puede hacer cualquier otra cosa mientras solo tiene que seguir monitoreando el contenido de retFile (también puede contener cualquier otra información que desee, como la hora de salida).
PD .: por cierto, codifiqué pensando en bash
fuente
Esto puede extenderse más allá de su pregunta, sin embargo, si le preocupa la cantidad de tiempo que se ejecutan los procesos, puede estar interesado en verificar el estado de los procesos en segundo plano en ejecución después de un intervalo de tiempo. Es bastante fácil verificar qué PID secundarios todavía se están utilizando
pgrep -P $$
, sin embargo, se me ocurrió la siguiente solución para verificar el estado de salida de esos PID que ya han expirado:que salidas:
Nota: Puede cambiar
$pids
a una variable de cadena en lugar de a una matriz para simplificar las cosas si lo desea.fuente
Mi solución fue usar una tubería anónima para pasar el estado a un circuito de monitoreo. No se utilizan archivos temporales para intercambiar estado, por lo que no hay nada que limpiar. Si no estaba seguro del número de trabajos en segundo plano, la condición de interrupción podría ser
[ -z "$(jobs -p)" ]
.fuente