Como posso matar e esperar por processos em segundo plano para terminar em um script de shell quando eu Ctrl + C é?

18

Estou tentando configurar um script de shell para que ele execute processos em segundo plano, e quando eu Ctrl c o script de shell, ele mata os filhos e sai .

O melhor que eu consegui fazer é isso. Parece que o kill 0 -INT também mata o script antes que a espera aconteça, portanto, o shell script morre antes que os filhos sejam concluídos.

Alguma idéia de como eu posso fazer este script de shell esperar que os filhos morram depois de enviar INT ?

#!/bin/bash
trap 'killall' INT

killall() {
    echo "**** Shutting down... ****"
    kill 0 -INT
    wait # Why doesn't this wait??
    echo DONE
}

process1 &
process2 &
process3 &

cat # wait forever
    
por slipheed 13.11.2012 / 03:18

5 respostas

18

Seu comando kill está invertido.

Como muitos comandos do UNIX, as opções que começam com menos devem vir primeiro, antes de outros argumentos.

Se você escrever

kill -INT 0

ele vê o -INT como uma opção e envia SIGINT para 0 ( 0 é um número especial que significa todos os processos no grupo de processos atual).

Mas se você escrever

kill 0 -INT

ele vê o 0 , decide que não há mais opções, então usa SIGTERM por padrão. E envia isso para o grupo de processos atual, o mesmo que se você fizesse

kill -TERM 0 -INT    

(também tentaria enviar SIGTERM para -INT , o que causaria um erro de sintaxe, mas envia SIGTERM para 0 primeiro e nunca chega tão longe.)

Portanto, seu script principal está recebendo um SIGTERM antes de executar os wait e echo DONE .

Adicionar

trap 'echo got SIGTERM' TERM

no topo, logo após

trap 'killall' INT

e execute-o novamente para provar isso.

Como Stephane Chazelas aponta, seus filhos de segundo plano ( process1 , etc.) irão ignorar SIGINT por padrão.

De qualquer forma, acho que enviar SIGTERM faria mais sentido.

Finalmente, não tenho certeza se kill -process group tem a garantia de ir primeiro aos filhos. Ignorar sinais durante o desligamento pode ser uma boa ideia.

Então tente isto:

#!/bin/bash
trap 'killall' INT

killall() {
    trap '' INT TERM     # ignore INT and TERM while shutting down
    echo "**** Shutting down... ****"     # added double quotes
    kill -TERM 0         # fixed order, send TERM not INT
    wait
    echo DONE
}

./process1 &
./process2 &
./process3 &

cat # wait forever
    
por 16.11.2012 / 17:48
9

Infelizmente, os comandos iniciados em segundo plano são configurados pelo shell para ignorar o SIGINT e, pior, eles não podem desanexá-lo com trap . Caso contrário, tudo o que você precisa fazer é

(trap - INT; exec process1) &
(trap - INT; exec process2) &
trap '' INT
wait

Como process1 e process2 obteriam o SIGINT quando você pressiona Ctrl-C, pois fazem parte do mesmo grupo de processos que é o grupo de processos em primeiro plano do terminal.

O código acima irá trabalhar com pdksh e zsh que, a esse respeito, não estão em conformidade com POSIX.

Com outros shells, você teria que usar outra coisa para restaurar o manipulador padrão para SIGINT como:

perl -e '$SIG{INT}=DEFAULT; exec "process1"' &

ou use um sinal diferente como SIGTERM.

    
por 13.11.2012 / 13:37
2

Para aqueles que querem apenas matar um processo e esperar que ele morra, mas não indefinidamente :

Aguarda no máximo 60 segundos por tipo de sinal.

Aviso: Esta resposta não está relacionada de forma alguma a capturar um sinal de eliminação e despachá-lo.

# close_app_sub GREP_STATEMENT SIGNAL DURATION_SEC
# GREP_STATEMENT must not match itself!
close_app_sub() {
    APP_PID=$(ps -x | grep "$1" | grep -oP '^\s*\K[0-9]+' --color=never)
    if [ ! -z "$APP_PID" ]; then
        echo "App is open. Trying to close app (SIGNAL $2). Max $3sec."
        kill $2 "$APP_PID"
        WAIT_LOOP=0
        while ps -p "$APP_PID" > /dev/null 2>&1; do
            sleep 1
            WAIT_LOOP=$((WAIT_LOOP+1))
            if [ "$WAIT_LOOP" = "$3" ]; then
                break
            fi
        done
    fi
    APP_PID=$(ps -x | grep "$1" | grep -oP '^\s*\K[0-9]+' --color=never)
    if [ -z "$APP_PID" ]; then return 0; else return "$APP_PID"; fi
}

close_app() {
    close_app_sub "$1" "-HUP" "60"
    close_app_sub "$1" "-TERM" "60"
    close_app_sub "$1" "-SIGINT" "60"
    close_app_sub "$1" "-KILL" "60"
    return $?
}

close_app "[f]irefox"

Seleciona o aplicativo para matar por nome ou argumentos. Mantenha os colchetes da primeira letra do nome do aplicativo para evitar a correspondência do próprio grep.

Com algumas alterações, você pode usar diretamente o PID ou uma declaração pidof process_name em vez da instrução ps .

Detalhes do código: O grep final é para obter o PID sem os espaços à direita.

    
por 07.02.2016 / 16:13
1

Se o que você deseja é gerenciar alguns processos em segundo plano, por que não usar os recursos de controle de tarefas bash?

$ gedit &
[1] 2581
$ emacs &
[2] 2594
$ jobs
[1]-  Running                 gedit &
[2]+  Running                 emacs &
$ jobs -p
2581
2594
$ kill -2 'jobs -p'
$ jobs
[1]-  Interrupt               gedit
[2]+  Done                    emacs
    
por 13.11.2012 / 14:28
0

Então eu mexi com isso também. No Bash, você pode definir funções e fazer coisas interessantes com elas. Meus colegas de trabalho e eu usamos terminator , então para execuções em lote normalmente geramos um monte de janelas terminator se queremos ver a saída (menos elegante que tmux tabs, mas você pode usar uma interface mais parecida com GUI haha) .

Aqui está a configuração que eu criei, bem como um exemplo de coisas que você pode executar:

#!/bin/bash
custom()
{
    terun 'something cool' 'yes'
    run 'xclock'
    terun 'echoes' 'echo Hello; echo Goodbye; read'
    func()
    {
        local i=0
        while :
        do
            echo "Iter $i"
            let i+=1
            sleep 0.25
        done
    }
    export -f func
    terun 'custom func' 'func'
}

# [ Setup ]
run()
{
    "$@" &
    # Give process some time to start up so windows are sequential
    sleep 0.05
}
terun()
{
    run terminator -T "$1" -e "$2"
}
finish()
{
    procs="$(jobs -p)"
    echo "Kill: $procs"
    # Ignore process that are already dead
    kill $procs 2> /dev/null
}
trap 'finish' 2

custom

echo 'Press <Ctrl+C> to kill...'
wait

Rodando

$ ./program_spawner.sh 
Press <Ctrl+C> to kill...
^CKill:  9835
9840
9844
9850
$ 

Editar @Maxim, acabei de ver a sua sugestão, o que a torna muito mais simples! Obrigado!

    
por 27.01.2014 / 00:46