(Inspirado pela resposta de Gilles)
Com o sinalizador ISIG
definido, a única maneira de o script Child
obter SIGINT
sem que seu pai receba SIGINT
é que ele esteja em seu próprio grupo de processos. Isso pode ser feito com a opção set -m
.
Se você ativar a opção -m
no script de shell Child
, ele executará o controle de tarefas sem ser interativo. Isso fará com que ele execute coisas em um grupo de processos separado, impedindo que o pai receba o SIGINT
quando o caractere INTR
for lido.
Aqui está a descrição POSIX da opção -m
:
-m
This option shall be supported if the implementation supports the User Portability Utilities option. All jobs shall be run in their own process groups. Immediately before the shell issues a prompt after completion of the background job, a message reporting the exit status of the background job shall be written to standard error. If a foreground job stops, the shell shall write a message to standard error to that effect, formatted as described by the jobs utility. In addition, if a job changes status other than exiting (for example, if it stops for input or output or is stopped by a SIGSTOP signal), the shell shall write a similar message immediately prior to writing the next prompt. This option is enabled by default for interactive shells.
A opção -m
é semelhante a -i
, mas não altera o comportamento do shell quase tanto quanto -i
.
Exemplo:
-
o script
Parent
:#!/bin/sh trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT echo "PARENT: pid=$$" echo "PARENT: Spawning child..." ./Child echo "PARENT: child returned" echo "PARENT: exiting normally"
-
o script
Child
:#!/bin/sh -m # ^^ # notice the -m option above! trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT echo "CHILD: pid=$$" echo "CHILD: hit enter to exit" read foo echo "CHILD: exiting normally"
Isto é o que acontece quando você aperta Controle + C enquanto Child
está esperando pela entrada:
$ ./Parent
PARENT: pid=12233
PARENT: Spawning child...
CHILD: pid=12234
CHILD: hit enter to exit
^CCHILD: caught SIGINT; exiting
PARENT: child returned
PARENT: exiting normally
Observe como o manipulador SIGINT
do pai nunca é executado.
Se preferir modificar Parent
em vez de Child
, você pode fazer isso:
-
o script
Parent
:#!/bin/sh trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT echo "PARENT: pid=$$" echo "PARENT: Spawning child..." sh -m ./Child # or 'sh -m -c ./Child' if Child isn't a shell script echo "PARENT: child returned" echo "PARENT: exiting normally"
-
o script
Child
(normal; sem necessidade de-m
):#!/bin/sh trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT echo "CHILD: pid=$$" echo "CHILD: hit enter to exit" read foo echo "CHILD: exiting normally"
Idéias alternativas
- Modifique os outros processos no grupo de processos em primeiro plano para ignorar
SIGINT
pela duração deChild
. Isso não resolve sua pergunta, mas pode conseguir o que você quer. - Modifique
Child
para:- Use
stty -g
para fazer backup das configurações atuais do terminal. - Execute
stty -isig
para não gerar sinais com os caracteresINTR
,QUIT
eSUSP
. - Em segundo plano, leia a entrada do terminal e envie os sinais você mesmo conforme apropriado (por exemplo, execute
kill -QUIT 0
quando Controle + \ for lido,kill -INT $$
quando Controle + C é lido). Isso não é trivial, e pode não ser possível fazer isso funcionar sem problemas se o scriptChild
ou qualquer coisa que ele executa for feito para ser interativo. - Restaure as configurações do terminal antes de sair (de preferência de uma interceptação em
EXIT
).
- Use
- O mesmo que # 2, exceto que, em vez de executar
stty -isig
, espere o usuário pressionar Enter ou alguma outra chave não especial antes de matarChild
. -
Escreva seu próprio utilitário
setpgid
em C, Python, Perl, etc., que você pode usar para chamarsetpgid()
. Aqui está uma implementação bruta de C:#define _XOPEN_SOURCE 700 #include <unistd.h> #include <signal.h> int main(int argc, char *argv[]) { // todo: add error checking void (*backup)(int); setpgid(0, 0); backup = signal(SIGTTOU, SIG_IGN); tcsetpgrp(0, getpid()); signal(SIGTTOU, backup); execvp(argv[1], argv + 1); return 1; }
Exemplo de uso de
Child
:#!/bin/sh [ "${DID_SETPGID}" = true ] || { # restart self after calling setpgid(0, 0) exec env DID_SETPGID=true setpgid "$0" "$@" # exec failed if control reached this point exit 1 } unset DID_SETPGID # do stuff here