procesos - ver ejecuciones en segundo plano linux
Obtener el código de salida de un proceso en segundo plano (11)
¡El pid de un proceso secundario retroactivo se almacena en $! . Puede almacenar todos los procesos secundarios en una matriz, por ejemplo, PIDS [] .
wait [-n] [jobspec or pid …]
Espere hasta que finalice el proceso hijo especificado por cada ID de proceso pid o especificación de trabajo jobspec y devuelva 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 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 ni jobspec ni pid especifica un proceso secundario activo del shell, el estado de retorno es 127.
Use el comando wait , puede esperar a que finalicen todos los procesos hijo, mientras tanto, puede obtener el estado de salida de cada proceso hijo a través de $? y almacenar 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
#!/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
solución02
#!/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
Tengo un comando CMD llamado desde mi script principal de bourne shell que lleva una eternidad.
Quiero modificar el script de la siguiente manera:
- Ejecute el comando CMD en paralelo como proceso en segundo plano ($ CMD &).
- En la secuencia de comandos principal, tenga un ciclo para monitorear el comando engendrado cada pocos segundos. El ciclo también hace eco de algunos mensajes para indicar el progreso del script.
- Salga del bucle cuando termine el comando generado.
- Capture e informe el código de salida del proceso generado.
¿Alguien puede darme consejos para lograr esto?
Así es como lo resolví 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
Cambiaría tu enfoque un poco. 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
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 mediante kill -0 <PID>
integrado (universal) o comprobando la existencia del /proc/<PID>
(específico de Linux) o utilizando los jobs
incorporados ( jobs -l
específicos de bash jobs -l
también informa el pid. En este caso, el tercer campo de la salida puede ser Stopped | Running | Done | Exit.).
Aquí está mi ejemplo.
El proceso lanzado 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 de launch.sh
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 obtener más información, consulte: Error al iniciar un proceso desde el script bash
Con este 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, 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 el tiempo de salida).
PD .: por cierto, codifiqué pensando en bash
Esto puede extenderse más allá de su pregunta; sin embargo, si le preocupa el tiempo que tardan los procesos, es posible que esté interesado en verificar el estado de los procesos en segundo plano después de un intervalo de tiempo. Es fácil verificar qué PID secundarios aún se están ejecutando utilizando pgrep -P $$
; sin embargo, se me ocurrió la siguiente solución para verificar el estado de salida de los PID que ya han expirado:
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
qué salidas:
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.
Nuestro equipo tenía la misma necesidad con una secuencia de comandos remota ejecutada por SSH que se agotaba 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}
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
#!/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 puedes usar la variable $ exit_status como quieras.
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
Use la cola para seguir la salida del proceso y salga cuando el proceso esté 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
1: En bash, $!
contiene el PID del último proceso en segundo plano que se ejecutó. Eso te dirá qué proceso supervisar, de todos modos.
4: wait <n>
espera hasta que finalice 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 que la wait
vuelve, el código de salida del proceso se devuelve en la variable $?
2, 3: ps
o ps | grep " $! "
ps | grep " $! "
puede indicarle si el proceso aún se está ejecutando. 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 tienes tiempo, puedes encontrar una manera más robusta de saber si el proceso aún se está ejecutando).
Aquí hay 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
#/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 $?