Como faço para matar processos / trabalhos em segundo plano quando sai meu script de shell?

Eu estou procurando uma maneira de limpar a bagunça quando meu script de nível superior sai.

Especialmente se eu quiser usar set -e , eu desejo que o processo de background morra quando o script sair.

Para limpar alguma bagunça, trap pode ser usada. Pode fornecer uma lista de coisas executadas quando um sinal específico chega:

 trap "echo hello" SIGINT 

mas também pode ser usado para executar algo se o shell sair:

 trap "killall background" EXIT 

É um builtin, então help trap lhe dará informações (funciona com o bash). Se você quer apenas matar trabalhos em segundo plano, você pode fazer

 trap 'kill $(jobs -p)' EXIT 

Cuidado para usar single ' , para evitar que o shell substitua o $() imediatamente.

Isso funciona para mim (melhorou graças aos comentadores):

 trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT 
  • kill -- -$$ envia um SIGTERM para todo o grupo de processos, matando também os descendentes.

  • A especificação do sinal EXIT é útil ao usar set -e (mais detalhes aqui ).

 trap "exit" INT TERM trap "kill 0" EXIT 

Por que converter INT e TERM para sair? Porque ambos devem acionar o kill 0 sem entrar em um loop infinito.

Por que triggersr kill 0 no EXIT ? Porque saídas de script normais também acionam kill 0 .

Por que kill 0 ? Porque os subshells nesteds precisam ser mortos também. Isso derrubará toda a tree de processos .

armadilha ‘kill $ (jobs -p)’ EXIT

Eu faria apenas pequenas alterações na resposta de Johannes e usaria jobs -pr para limitar o kill a processos em execução e adicionar mais alguns sinais à lista:

 trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT 

A trap 'kill 0' SIGINT SIGTERM EXIT solução trap 'kill 0' SIGINT SIGTERM EXIT descrita na resposta do @tkland é realmente legal, mas o último Bash falha com uma falha de segmantação ao usá-la. Isso porque o Bash, a partir do 4.3, permite a recursion de armadilhas, que se torna infinita neste caso:

  1. o processo shell recebe SIGINT ou SIGTERM ou EXIT ;
  2. o sinal fica preso, executando kill 0 , que envia SIGTERM para todos os processos no grupo, incluindo o próprio shell;
  3. vá para 1 🙂

Isso pode ser contornado manualmente, desregistrando a armadilha:

 trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT 

A maneira mais extravagante, que permite imprimir o sinal recebido e evita mensagens “Terminadas:”:

 #!/usr/bin/env bash trap_with_arg() { # from https://stackoverflow.com/a/2183063/804678 local func="$1"; shift for sig in "$@"; do trap "$func $sig" "$sig" done } stop() { trap - SIGINT EXIT printf '\n%s\n' "recieved $1, killing children" kill -s SIGINT 0 } trap_with_arg 'stop' EXIT SIGINT SIGTERM SIGHUP { i=0; while (( ++i )); do sleep 0.5 && echo "a: $i"; done } & { i=0; while (( ++i )); do sleep 0.6 && echo "b: $i"; done } & while true; do read; done 

UPD : adicionado exemplo mínimo; function de stop melhorada para aviar os sinais desnecessários e ocultar as mensagens “Terminadas:” da saída. Obrigado Trevor Boyd Smith pelas sugestões!

Para estar no lado seguro, acho melhor definir uma function de limpeza e chamá-la de trap:

 cleanup() { local pids=$(jobs -pr) [ -n "$pids" ] && kill $pids } trap "cleanup" INT QUIT TERM EXIT [...] 

ou evitando a function completamente:

 trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...] 

Por quê? Porque simplesmente usando trap 'kill $(jobs -pr)' [...] assume-se que haverá tarefas em segundo plano quando a condição de armadilha é sinalizada. Quando não houver trabalhos, você verá a seguinte mensagem (ou similar):

 kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec] 

porque os jobs -pr estão vazios – acabei nessa ‘armadilha’ (trocadilho intencional).

Uma boa versão que funciona no Linux, BSD e MacOS X. Primeiro, tenta enviar o SIGTERM e, se não der certo, mata o processo após 10 segundos.

 KillJobs() { for job in $(jobs -p); do kill -s SIGTERM $job > /dev/null 2>&1 || (sleep 10 && kill -9 $job > /dev/null 2>&1 &) done } TrapQuit() { # Whatever you need to clean here KillJobs } trap TrapQuit EXIT 

Por favor, note que os trabalhos não incluem processos de filhos grandes.

Outra opção é ter o script definido como líder do grupo de processos e interceptar um killpg no grupo de processos ao sair.

Então, faça o script do carregamento do script. Execute um comando killall (ou o que estiver disponível no seu sistema operacional) que é executado assim que o script é concluído.

jobs -p não funciona em todos os shells se for chamado em um sub-shell, possivelmente a menos que sua saída seja redirecionada para um arquivo, mas não para um pipe. (Eu suponho que foi originalmente destinado apenas para uso interativo.)

E quanto ao seguinte:

 trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...] 

A chamada para “jobs” é necessária com o dash shell do Debian, que falha ao atualizar o job atual (“%%”) se ele estiver faltando.

Fiz uma adaptação da resposta da @tkland combinada com o conhecimento de http://veithen.github.io/2014/11/16/sigterm-propagation.html quando percebi que a trap não aciona se estou executando um primeiro plano processo (não baseado em & ):

 #!/bin/bash # killable-shell.sh: Kills itself and all children (the whole process group) when killed. # Adapted from http://stackoverflow.com/a/2173421 and http://veithen.github.io/2014/11/16/sigterm-propagation.html # Note: Does not work (and cannot work) when the shell itself is killed with SIGKILL, for then the trap is not triggered. trap "trap - SIGTERM && echo 'Caught SIGTERM, sending SIGTERM to process group' && kill -- -$$" SIGINT SIGTERM EXIT echo $@ "$@" & PID=$! wait $PID trap - SIGINT SIGTERM EXIT wait $PID 

Exemplo disso funcionando:

 $ bash killable-shell.sh sleep 100 sleep 100 ^Z [1] + 31568 suspended bash killable-shell.sh sleep 100 $ ps aux | grep "sleep" niklas 31568 0.0 0.0 19640 1440 pts/18 T 01:30 0:00 bash killable-shell.sh sleep 100 niklas 31569 0.0 0.0 14404 616 pts/18 T 01:30 0:00 sleep 100 niklas 31605 0.0 0.0 18956 936 pts/18 S+ 01:30 0:00 grep --color=auto sleep $ bg [1] + 31568 continued bash killable-shell.sh sleep 100 $ kill 31568 Caught SIGTERM, sending SIGTERM to process group [1] + 31568 terminated bash killable-shell.sh sleep 100 $ ps aux | grep "sleep" niklas 31717 0.0 0.0 18956 936 pts/18 S+ 01:31 0:00 grep --color=auto sleep