saída confirmada usando armadilha

9

Estou tentando interceptar o Ctrl+Csinal pedindo uma confirmação do usuário. A parte de captura funciona bem. Mas quando o sinal fica preso, ele não retorna à execução normal. Em vez disso, fecha o script. Como fazê-lo retomar a execução quando o usuário pressiona não.

aqui está o meu código

hell()
{
echo "Do you want to quit? Press 1 for yes and 0 for no";
read n;
if [ $n == 1 ]; then
exit 1;
fi
}

trap "hell" SIGINT

find /
CHID
fonte

Respostas:

12

O que está acontecendo

Quando você pressiona Ctrl+ C, o SIGINTsinal é entregue a todo o grupo de processos em primeiro plano . Aqui é enviado para o findprocesso e o processo de shell de chamada. findreage saindo imediatamente, e a concha reage chamando a armadilha.

Se o código na armadilha retornar (ou seja, não chamar exit), a execução continuará com o comando após o que foi interrompido pelo sinal. Aqui, após o findcomando chegar o final do script, o script sai imediatamente de qualquer maneira. Mas você pode ver a diferença entre inserir 0 e 1 adicionando outro comando:

find /
echo "find returned $?"

Uma maneira de fazer o que você quer (mas provavelmente não deveria)

Você pode fazer o que você quiser; mas essa parte da minha resposta é mais sobre descobrir a programação de shell do que resolver um problema real.

  • Por uma questão de design, um sinal reinicializável não é o que você normalmente esperaria no tipo de programas relativamente simples que normalmente são os shell scripts. A expectativa é que Ctrl+ Cmate o script.
  • Como você verá abaixo, ele estende os recursos do shell um pouco longe de qualquer maneira.

Se você quiser evitar o abate find, é necessário iniciá-lo no fundo : find / &. Em seguida, use o waitbuilt-in para esperar que ele saia normalmente. Um sinal interromperá o waitbuilt-in, que você pode executar em loop até receber o sinal que deseja propagar. Então use killpara matar o trabalho.

hell () {
  echo "Do you want to quit? Press 1 for yes and 0 for no"
  read n
  if [ "$n" = 1 ]; then
    # Kill the job if it's running, then exit
    if [ -n "$job_pid" ]; then kill $job_pid; fi
    exit 1
  fi
}

job_pid=
trap "hell" SIGINT

# Start a demo job in the background
for i in 1 2 3 4 5; do date; sleep 1; done &
job_pid=$!
# Call wait in a loop; wait will return 0 if the job exits, and 128+$signum if interrupted by a signal.
while ! wait; do
  echo "resuming wait"
done
job_pid=
echo last exit code: $?

Existem limitações para essa abordagem no shell:

  • Existe uma condição de corrida: se você pressionar Ctrl+ Clogo após o trabalho terminar, mas antes da job_pid=linha, o manipulador de sinais tentará matar $jobpid, mas o processo não existe mais (mesmo como um zumbi, porque waitjá o colheu) e o processo O ID pode ter sido reutilizado por outro processo. Isso não é facilmente corrigível no shell (talvez configurando um manipulador para SIGCHLD?).
  • Se você precisar do status de retorno do trabalho, precisará usar o wait $job_pidformulário. Mas você não pode distinguir “ waitfoi interrompido por um sinal” de “o trabalho foi morto por um sinal” (nem de “o trabalho foi finalizado por conta própria com um status de retorno ≥128”, mas isso é um fato geral em geral programação).
  • Isso não se estenderá facilmente se houver vários submobs. Observe que o comportamento de traps e sinais geralmente é surpreendente quando você vai além do básico na maioria das implementações de shell (apenas o ksh faz isso bem).

Para superar essas limitações, use uma linguagem mais sofisticada, como Perl ou Python.

Gilles 'SO- parar de ser mau'
fonte