Se algum processo filho gerado falhar, mate tudo e saia

9

No meu script, divido um conjunto de dados em input_aa, input_ab, etc. Em seguida, executo cada um deles através do mesmo script Python, como tal:

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

Minha pergunta é dupla: como detectar que um processo Python falhou e, quando detectado, como matar todos os filhos gerados e sair do script com falha?

Befall
fonte

Respostas:

10

Você pode usar um grupo de processos:

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

set -m(e o recurso de shell POSIX opcional, recurso de shell Unix necessário) executa tarefas em seu próprio grupo de processos. Em bash, yash, zsh, mksh, que é o emprego da subcamada onde set -mé habilitado para o exterior (...)e todos os processos criados dentro que vai ser colocado no mesmo grupo processo.

Para dashe outros ashshells baseados, isso funciona apenas no processo de shell de nível superior. Portanto, esse código funcionará, a menos que seja colocado em um subshell.

Isso não funcionará na AT&T kshou no antigo shell SysV / Bourne.

kill 0 envia um sinal SIGTERM para todos os membros do grupo de processos atual.

Stéphane Chazelas
fonte
Na festança. Por que eu incluí um shebang - o shell necessário não está claro. Boa resposta
jim mcnamara
@jimmcnamara, que funciona em bash, dash, yash, mksh, zsh. Basicamente, qualquer shell POSIX, exceto AT&T ksh. set -mé (sub-) especificado no POSIX, mas como um recurso opcional.
Stéphane Chazelas
Eu uso o Solaris. / bin / sh não voará.
precisa saber é o seguinte
@jimmcnamara, no / bin / sh no Solaris 10 e anterior é o shell Bourne (não é um shell POSIX) e no 11, AT&T ksh. Como eu disse, ele funciona em bash, dash, yash, mksh, zsh.
Stéphane Chazelas 15/15/15
1
@mikeserv, que repararia o processo para 1, mas não o retiraria do grupo de processos. kill 0mata todos os membros do grupo de processos, seja qual for o pai. Veja ps -jpara ver os IDs do grupo de processos.
Stéphane Chazelas
3

Isto é um exemplo. JOGUE com este primeiro para obter exatamente o que você precisa. Não pode quebrar tanto quanto é.

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

Está configurado para falhar deliberadamente porque ls oinklefalhará (na minha máquina).

Quando você obtém o que precisa depois de mexer no script inicial --- Alteração:

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

para:

for part in input_* 

mudança:

sleep $i && ls oinkle 

para:

python3 $part 

Os redirecionamentos estão lá para salvar logs. Você pode não querer.

jim mcnamara
fonte
É um pouco atrevido. Se um dos trabalhos falhar antes de todos os outros serem iniciados, você killfilepoderá não conter todos os pids dos trabalhos que foram iniciados.
Stéphane Chazelas
Algumas más práticas como: variáveis não cotadas, uso de números de sinal em vez de nomes, sinal de uso 6 (ABRT em amd64 Linux, por exemplo) em vez de USR1 / USR2 como um sinal de usuário, [ $? -ne 0 ]...
Stéphane Chazelas