Si falla algún proceso hijo generado, elimine todo y salga

9

En mi script dividí un conjunto de datos en input_aa, input_ab, etc. Luego, ejecuto cada uno a través del mismo script Python, como tal:

# Execute program on each split file
for part in input_*; do
        python3 $part &
done
wait

Mi pregunta es doble: ¿cómo detecto que un proceso de Python ha fallado y, cuando lo detecto, cómo mato a todos los niños engendrados y salgo del script con un error?

Acontecer
fuente

Respuestas:

10

Podría usar un grupo de procesos:

set -m
(
   for part in input_*; do
     (python3 "$part" || kill 0) &
   done
   wait
)

set -m(y la función de shell POSIX opcional, la función de shell Unix requerida) ejecuta trabajos en su propio grupo de procesos. En bash, yash, zsh, mksh, eso es puestos de trabajo del subnivel donde set -mestá habilitado para el exterior (...)y todos los procesos creados en que se colocará en el mismo grupo de procesos.

Para dashy otros ashshells basados, eso solo funciona en el proceso de shell de nivel superior. Por lo tanto, ese código funcionará a menos que se coloque en una subshell.

Eso no funcionará en AT&T ksho en el viejo shell SysV / Bourne.

kill 0 envía una señal SIGTERM a todos los miembros del grupo de proceso actual.

Stéphane Chazelas
fuente
En bash. Por qué incluí un shebang: el shell requerido no está claro. Buena respuesta
jim mcnamara
@jimmcnamara, que las obras en bash, dash, yash, mksh, zsh. Básicamente cualquier shell POSIX pero AT&T ksh. set -mse especifica (debajo de) en POSIX pero como una característica opcional.
Stéphane Chazelas
Yo uso Solaris. / bin / sh no volará.
jim mcnamara
@jimmcnamara, no / bin / sh en Solaris 10 y anteriores es el shell Bourne (no un shell POSIX), y el 11, AT&T ksh. Como dije, funciona en bash, dash, yash, mksh, zsh.
Stéphane Chazelas
1
@mikeserv, eso devolvería el proceso a 1, pero no lo sacaría del grupo de procesos. kill 0mata a todos los miembros del grupo de proceso, sea cual sea su padre. Consulte ps -jpara ver los ID de grupo de proceso.
Stéphane Chazelas
3

Esto es un ejemplo. JUEGA con esto primero para obtener exactamente lo que necesitas. No puede romperse mucho como está.

#!/bin/bash
# Example of killing off all children

> killfile
> outfile.err
kill_em()
{
   echo 'killing all children ' > 2
   while read pid
   do
      kill -0 $pid && kill -9 $pid  # if still running kill it
   done < killfile
   exit 1
}

export grandparentpid=$$
trap 'kill_em' 6
for i in 2 2 3 4 5 6 7 8 9 10
do
        ( sleep $i && ls oinkle  >> outfile 2>> outfile.err &
          pid=$!
          echo $pid >> killfile
          wait $!
          [ $? -ne 0 ] && kill -6 $grandparentpid
        ) &
done
wait

Esto está configurado para fallar deliberadamente porque ls oinklefallará (en mi máquina).

Cuando obtienes lo que necesitas después de jugar con el script de inicio --- Cambiar:

for i in 2 2 3 4 5 6 7 8 9 10

a:

for part in input_* 

cambio:

sleep $i && ls oinkle 

a:

python3 $part 

Las redirecciones están ahí para guardar registros. Puede que no los quieras.

jim mcnamara
fuente
Es un poco picante. Si uno de los trabajos falla antes de que todos los demás hayan comenzado, es killfileposible que no contenga todos los pids de los trabajos que se han iniciado.
Stéphane Chazelas
Algunas malas prácticas como: variables sin comillas, uso de números de señal en lugar de nombres, uso de la señal 6 (ABRT en Linux amd64 por ejemplo) en lugar de USR1 / USR2 como señal de usuario, [ $? -ne 0 ]...
Stéphane Chazelas