Se a mudança para xterm
for uma opção, você pode usar o hack abaixo. Há algumas ressalvas embora. Depois de abordar a maioria deles, a solução acaba sendo bastante complicada, veja o roteiro final no final.
xterm -e 'trap "" HUP; your-application'
Ao receber a instrução para fechar a partir do gerenciador de janelas, xterm
enviará um SIGHUP para o grupo de processos do seu aplicativo e só sairá quando o processo retornar.
Isso pressupõe que o seu aplicativo não redefine o manipulador de SIGHUP e possa ter efeitos colaterais indesejados para os filhos do seu aplicativo.
Ambos parecem ser um problema se o seu aplicativo for tmux
.
Para contornar isso, você poderia fazer:
xterm -e sh -c 'bash -mc tmux <&1 & trap "" HUP; wait'
Dessa forma, tmux
seria iniciado em um grupo de processos diferente, portanto, somente o sh
receberia o SIGHUP (e o ignoraria).
Agora, isso não se aplica a tmux
que redefine o manipulador para esses sinais de qualquer maneira, mas no caso geral, dependendo da implementação de sh
, os sinais SIGINT, SIGQUIT e geralmente ambos serão ignorados seu aplicativo como bash
é iniciado como um comando assíncrono de um sh
não interativo. Isso significa que você não pode interromper seu aplicativo com Ctrl + C ou Ctrl + \ .
Esse é um requisito POSIX. Alguns shells como mksh
não o honram (pelo menos não as versões atuais), ou apenas parcialmente como dash
que faz isso para o SIGINT, mas não o SIGQUIT. Então, se mksh
estiver disponível, você poderia fazer:
xterm -e mksh -c 'bash -mc your-application <&1 & trap "" HUP; wait'
(embora isso possa não funcionar em versões futuras de mksh
se eles decidirem corrigir essa não conformidade).
Ou, se você não pode garantir que mksh
ou bash
estarão disponíveis ou preferiria não depender de um comportamento que possa mudar no futuro, você pode fazer seu trabalho manualmente com perl
e, por exemplo, escrever um script% wrapper unclosable-xterm
como:
#! /bin/sh -
[ "$#" -gt 0 ] || set -- "${SHELL:-/bin/sh}"
exec xterm -e perl -MPOSIX -e '
$pid = fork;
if ($pid == 0) {
setpgrp or die "setpgrp: $!";
tcsetpgrp(0,getpid) or die "tcsetpgrp: $!";
exec @ARGV;
die "exec: $!";
}
die "fork: $!" if $pid < 0;
$SIG{HUP} = "IGNORE";
waitpid(-1,WUNTRACED)' "$@"
(a ser chamado como unclosable-xterm your-application and its args
).
Agora, outro efeito colateral é que o novo grupo de processos que estamos criando e colocando em primeiro plano (com bash -m
ou setpgrp
+ tcsetpgrp
acima) não é mais o grupo de processos líder da sessão. Grupo de processos órfão (há um pai supostamente cuidando dele agora ( sh
ou perl
)).
O que isto significa é que ao pressionar Ctrl + Z , esse processo será suspenso. Aqui, nosso pai descuidado simplesmente sairá, o que significa que o grupo de processos receberá um SIGHUP (e esperamos que morra).
Para evitar isso, poderíamos simplesmente ignorar o SIGTSTP no processo filho, mas se o seu-aplicativo for um shell interativo, para algumas implementações como mksh
, yash
ou rc
, Ctrl- Z não funciona nem para as tarefas que executam.
Ou podemos implementar um pai mais cuidadoso que recomeça o filho toda vez que ele for interrompido, como:
#! /bin/sh -
[ "$#" -gt 0 ] || set -- "${SHELL:-/bin/sh}"
exec xterm -e perl -MPOSIX -e '
$pid = fork;
if ($pid == 0) {
setpgrp or die "setpgrp: $!";
tcsetpgrp(0,getpid) or die "tcsetpgrp: $!";
exec @ARGV;
die "exec: $!";
}
die "fork: $!" if $pid < 0;
$SIG{HUP} = "IGNORE";
while (waitpid(-1,WUNTRACED) > 0 && WIFSTOPPED(${^CHILD_ERROR_NATIVE})) {
kill "CONT", -$pid;
}' "$@"
Outro problema é que se xterm
desaparecer por outro motivo que o fechar do gerenciador de janelas, por exemplo, se xterm
for eliminado ou perder a conexão com o servidor X (por causa de xkill
, a ação destruir do gerenciador de janelas, ou o servidor X trava por exemplo), então esses processos não morrerão como SIGHUP também seria usado nesses casos para terminá-los. Para contornar isso, você poderia usar poll()
no dispositivo terminal (que seria demolido quando xterm
fosse):
#! /bin/sh -
[ "$#" -gt 0 ] || set -- "${SHELL:-/bin/sh}"
exec xterm -e perl -w -MPOSIX -MIO::Poll -e '
$pid = fork; # start the command in a child process
if ($pid == 0) {
setpgrp or die "setpgrp: $!"; # new process group
tcsetpgrp(0,getpid) or die "tcsetpgrp: $!"; # in foreground
exec @ARGV;
die "exec: $!";
}
die "fork: $!" if $pid < 0;
$SIG{HUP} = "IGNORE"; # ignore SIGHUP in the parent
$SIG{CHLD} = sub {
if (waitpid(-1,WUNTRACED) == $pid) {
if (WIFSTOPPED(${^CHILD_ERROR_NATIVE})) {
# resume the process when stopped
# we may want to do that only for SIGTSTP though
kill "CONT", -$pid;
} else {
# exit when the process dies
exit;
}
}
};
# watch for terminal hang-up
$p = IO::Poll->new;
$p->mask(STDIN, POLLERR);
while ($p->poll <= 0 || $p->events(STDIN) & POLLHUP == 0) {};
kill "HUP", -$pid;
' "$@"