Evita que um fork de shell demore mais do que seu iniciador?

3

Se eu tiver um script Bash como:

function repeat {
    while :; do
        echo repeating; sleep 1
    done
}
repeat &
echo running once

running once é impresso uma vez, mas a bifurcação de repeat dura para sempre, imprimindo infinitamente.

Como devo evitar que repeat continue a ser executado depois que o script que o criou saiu?

Pensei que talvez instanciar explicitamente um novo interpretador bash -c o forçaria a sair porque seu pai desapareceu, mas acho que processos órfãos foram adotados por init ou PID1.

Testando isso usando outro arquivo:

# repeat.bash
while :; do echo repeating; sleep 1; done

# fork.bash
bash -c "./repeat.bash & echo an exiting command"

A execução de ./fork.bash ainda faz com que repeat.bash continue sendo executado em segundo plano para sempre.

A solução simples e preguiçosa é adicionar a linha a fork.bash :

pkill repeat.bash

Mas é melhor que você não tenha outro processo importante com esse nome ou que também seja obliterado.

  1. Gostaria de saber se existe uma maneira melhor ou mais aceita de lidar com trabalhos em segundo plano em shells bifurcados que devem sair quando o script (ou processo) que os criou sair?

  2. Se não houver uma maneira melhor do que cegamente pkilling de todos os processos com o mesmo nome, como um trabalho repetitivo ao lado de algo como um servidor da Web deve ser tratado para sair? Eu quero evitar um trabalho cron porque o script está em um repositório git , e o código deve ser autônomo sem alterar arquivos do sistema em /etc/ .

por cat 11.05.2017 / 04:06

2 respostas

4

Isso mata o processo em segundo plano antes do script sair:

trap '[ "$pid" ] && kill "$pid"' EXIT

function repeat {
    while :; do
        echo repeating; sleep 1
    done
}
repeat &
pid=$!
echo running once

Como funciona

  • trap '[ "$pid" ] && kill "$pid"' EXIT

    Isso cria uma armadilha . Sempre que o script estiver prestes a sair, os comandos entre aspas simples serão executados. Esse comando verifica se a variável% shellpid foi atribuída a um valor não vazio. Se tiver, o processo associado a pid será eliminado.

  • pid=$!

    Isso salva o ID do processo do comando antecedente anterior ( repeat & ) na variável do shell pid .

Melhoria

Como Patrick aponta nos comentários, há uma chance de que o script possa ser morto após o processo em background começa, mas antes a variável pid é definida. Podemos lidar com esse caso com este código:

my_exit() {
    [ "$racing" ] && pid=$!
    [ "$pid" ] && kill "$pid"
}
trap my_exit EXIT

function repeat {
    while :; do
        echo repeating; sleep 1
    done
}

racing=Y
repeat &
pid=$!
racing=

echo running once
    
por 11.05.2017 / 05:32
2

O shell tcsh tem um comando hup para dizer ao shell para matar o comando ao sair:

hup mycommand...

Isso não ajuda na execução da função, pois o tcsh não tem suporte para funções.

Em zsh , quando o controle de trabalho está ativado, isso é feito por padrão para todos os trabalhos em segundo plano.

$ zsh -c 'set -m; sleep 2 &'; ps
[1] 23672
zsh:1: warning: 1 jobs SIGHUPed

Ativar o controle de tarefas em scripts pode ter efeitos colaterais.

bash também pode fazê-lo, mas somente quando interativo (apesar de chamá-lo com -i em um script também o fará) e somente quando um shell de login (!?). E você precisa ativar a opção huponexit . Então:

 bash -O huponexit -lic 'sleep 2 &'

Isso tem alguns efeitos colaterais, embora provavelmente não seja uma boa ideia.

Outra opção com zsh seria matar todos os processos filhos em execução conhecidos (em qualquer caso, não podemos saber facilmente sobre netos (nem que nós sempre quereríamos matá-los)) ao sair. zsh torna isso disponível no array associativo $jobstates :

TRAPEXIT() kill -s HUP ${${jobstates[(R)running:*]/#*:/}/%=*/}
trap exit INT HUP TERM

Outros shells não expõem sua lista de filhos, mas você pode matar os processos cujo ppid é $$ , como:

trap 'pkill -HUP -P "$$"' EXIT INT HUP TERM
    
por 11.05.2017 / 18:31