O que acontece é que tanto bash
e ping
receber a SIGINT ( bash
sendo não interativa, tanto ping
e bash
executados no mesmo grupo de processos que foi criado e definido como grupo de processo de primeiro plano do terminal pelo shell interativo você executou o script a partir).
No entanto, bash
lida com o SIGINT de forma assíncrona, somente após a saída do comando em execução no momento. bash
só sai ao receber esse SIGINT se o comando atualmente em execução morrer de um SIGINT (ou seja, seu status de saída indica que ele foi morto pelo SIGINT).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Acima, bash
, sh
e sleep
receber SIGINT quando eu pressionar Ctrl-C, mas sh
saídas normalmente com um código de saída 0, de modo bash
ignora a SIGINT, que é por isso que vemos "aqui".
ping
, pelo menos o de iputils, se comporta assim. Quando interrompido, ele imprime estatísticas e sai com um status de saída 0 ou 1, dependendo se seus pings foram ou não respondidos. Portanto, quando você pressiona Ctrl-C enquanto ping
está em execução, bash
observa que você pressionou Ctrl-C
seus manipuladores SIGINT, mas desde que ping
sai normalmente, bash
não sai.
Se você adicionar um sleep 1
nesse loop e pressionar Ctrl-C
enquanto sleep
estiver em execução, porque sleep
não possui um manipulador especial no SIGINT, ele morrerá e informará bash
que morreu de um SIGINT e, nesse caso bash
, sairá (ele realmente se matará com o SIGINT, de modo que para relatar a interrupção ao pai).
Quanto a por que bash
se comporta assim, não tenho certeza e observo que o comportamento nem sempre é determinístico. Acabei de fazer a pergunta na bash
lista de discussão de desenvolvimento ( atualização : @Jilles agora identificou o motivo em sua resposta ).
O único outro shell que descobri que se comporta de maneira semelhante é o ksh93 (o Update, como mencionado pelo @Jilles, o FreeBSDsh
). Lá, o SIGINT parece ser claramente ignorado. E ksh93
sai sempre que um comando é morto pelo SIGINT.
Você obtém o mesmo comportamento bash
acima, mas também:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Não gera "teste". Ou seja, ele sai (se matando com o SIGINT lá) se o comando que ele estava esperando morre do SIGINT, mesmo que ele próprio não tenha recebido esse SIGINT.
Uma solução alternativa seria adicionar um:
trap 'exit 130' INT
Na parte superior do script, para forçar bash
a saída ao receber um SIGINT (observe que, em qualquer caso, o SIGINT não será processado de forma síncrona, somente após a saída do comando em execução no momento).
Idealmente, gostaríamos de informar aos nossos pais que morremos de um SIGINT (para que, se for outro bash
script, por exemplo, esse bash
script também seja interrompido). Fazer um exit 130
não é o mesmo que morrer com o SIGINT (embora algumas conchas tenham o $?
mesmo valor nos dois casos), no entanto, é frequentemente usado para relatar uma morte pelo SIGINT (em sistemas onde o SIGINT é 2, o que é mais).
No entanto bash
, para o ksh93
FreeBSD sh
, isso não funciona. Esse status de saída 130 não é considerado como uma morte pelo SIGINT e um script pai não seria interrompido lá.
Portanto, uma alternativa possivelmente melhor seria nos matar com o SIGINT ao receber o SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
for f in *.txt; do vi "$f"; cp "$f" newdir; done
. Se o usuário digitar Ctrl + C durante a edição de um dos arquivos,vi
apenas exibirá uma mensagem. Parece razoável que o loop continue depois que o usuário terminar de editar o arquivo. (E sim, eu sei que você poderia dizervi *.txt; cp *.txt newdir
, estou apenas a apresentação dofor
circuito como um exemplo.)vi
(vim
pelo menos) desabilite o ttyisig
durante a edição (no entanto, não é possível quando você executa:!cmd
, e isso se aplica muito nesse caso).ping
sai com 0 depois de receber o SIGINT. Eu encontrei um comportamento semelhante quando um script bash contém emsudo
vez deping
, massudo
sai com 1 após receber o SIGINT. unix.stackexchange.com/questions/479023/…A explicação é que o bash implementa o WCE (espera e saída cooperativa) para SIGINT e SIGQUIT em http://www.cons.org/cracauer/sigint.html . Isso significa que, se o bash receber SIGINT ou SIGQUIT enquanto aguarda a saída de um processo, ele aguardará até o processo sair e sairá se o processo sair nesse sinal. Isso garante que os programas que usam SIGINT ou SIGQUIT em sua interface com o usuário funcionem conforme o esperado (se o sinal não fez o programa terminar, o script continuará normalmente).
Uma desvantagem aparece nos programas que capturam SIGINT ou SIGQUIT, mas terminam por causa disso, mas usando uma saída normal () em vez de reenviar o sinal para si mesmos. Talvez não seja possível interromper scripts que chamam esses programas. Eu acho que a correção real existe em programas como o ping e o ping6.
Comportamento semelhante é implementado pelo ksh93 e pelo FreeBSD / bin / sh, mas não pela maioria dos outros shells.
fonte
exit(130)
por exemplo, se você interrompermksh -c 'sleep 10;:'
).Como você supõe, isso se deve ao envio do SIGINT ao processo subordinado, e o shell continuando depois que o processo é encerrado.
Para lidar com isso de uma maneira melhor, você pode verificar o status de saída dos comandos em execução. O código de retorno do Unix codifica o método pelo qual um processo foi encerrado (chamada ou sinal do sistema) e qual valor foi passado
exit()
ou qual sinal encerrou o processo. Tudo isso é bastante complicado, mas a maneira mais rápida de usá-lo é saber que um processo que foi encerrado por sinal terá um código de retorno diferente de zero. Portanto, se você verificar o código de retorno em seu script, poderá sair de si mesmo se o processo filho tiver sido encerrado, eliminando a necessidade de deselegâncias, comosleep
chamadas desnecessárias . Uma maneira rápida de fazer isso em todo o script é usarset -e
, embora possa exigir alguns ajustes para comandos cujo status de saída é esperado como diferente de zero.fonte
ping
retorna com um status de saída 0 ao receber o SIGINT e, embash
seguida, ignora o SIGINT que ele próprio recebeu, se for esse o caso. Adicionar um "set -e" ou verificar o status de saída não ajudará aqui. Adicionar uma armadilha explícita no SIGINT ajudaria.O terminal percebe o controle-c e envia um
INT
sinal ao grupo de processos em primeiro plano, que aqui inclui o shell, poisping
não criou um novo grupo de processos em primeiro plano. Isso é fácil de verificar capturandoINT
.Se o comando que está sendo executado criou um novo grupo de processos em primeiro plano, o controle-c irá para esse grupo de processos, e não para o shell. Nesse caso, o shell precisará inspecionar os códigos de saída, pois não será sinalizado pelo terminal.
(
INT
Handling em conchas podem ser fabulosamente complicado, pelo caminho, como o shell às vezes precisa ignorar o sinal, e às vezes não Fonte mergulho se curioso, ou ponderar:.tail -f /etc/passwd; echo foo
)fonte
ping
não tem motivos para iniciar um novo grupo de processos aqui e a versão do ping (iputils no Debian) com a qual eu posso reproduzir o problema do OP não cria um grupo de processos.Bem, eu tentei adicionar um
sleep 1
ao script bash, e bang!Agora eu consigo parar com dois Ctrl+C.
Ao pressionar Ctrl+C, um
SIGINT
sinal é enviado para o processo atualmente executado, cujo comando foi executado dentro do loop. Em seguida, o processo de subshell continua executando o próximo comando no loop, que inicia outro processo. Para poder parar o script, é necessário enviar doisSIGINT
sinais, um para interromper o comando atual em execução e outro para interromper o processo de subcamação .No script sem a
sleep
chamada, pressionar Ctrl+Cmuito rápido e muitas vezes parece não funcionar, e não é possível sair do loop. Meu palpite é que pressionar duas vezes não é rápido o suficiente para chegar no momento certo entre a interrupção do processo executado atualmente e o início do próximo. Cada Ctrl+Cpressionado envia umSIGINT
para um processo executado dentro do loop, mas também não para o subshell .No script com
sleep 1
, essa chamada suspenderá a execução por um segundo e, quando interrompida pelo primeiro Ctrl+C(primeiroSIGINT
), o subshell levará mais tempo para executar o próximo comando. Então agora, o segundo Ctrl+C(segundoSIGINT
) irá para o subshell , e a execução do script terminará.fonte
Tente o seguinte:
Agora mude a primeira linha para:
e tente novamente - veja se o ping está agora interrompível.
fonte
por exemplo,
pgrep -f firefox
cumprirá o PID da execuçãofirefox
e salvará esse PID em um arquivo chamadoany_file_name
. O comando 'sed' adicionará okill
no início do número PID no arquivo 'any_file_name'. A terceira linha apresentará oany_file_name
arquivo executável. Agora, a linha eliminará o PID disponível no arquivoany_file_name
. Escrever as quatro linhas acima em um arquivo e executar esse arquivo pode fazer o Control- C. Trabalhando absolutamente bem para mim.fonte
Se alguém estiver interessado em uma correção para esse
bash
recurso, e não tanto na filosofia por trás dele , aqui está uma proposta:Não execute o comando problemático diretamente, mas a partir de um wrapper que a) aguarde o término b) não mexa nos sinais ec) não implemente o mecanismo WCE, mas simplesmente morre ao receber a
SIGINT
.Esse invólucro pode ser feito com
awk
+ suasystem()
função.Coloque um script como o OP:
fonte
Você é vítima de um conhecido erro do bash. O Bash controla os scripts, o que é um erro.
O que acontece é que o bash executa os programas externos em um grupo de processos diferente do usado para o próprio script. Como o grupo de processos TTY é definido como o grupo de processos do primeiro plano atual, apenas esse processo é eliminado e o loop no script de shell continua.
Para verificar: Busque e compile um Bourne Shell recente que implemente o pgrp (1) como um programa interno, adicione um / bin / sleep 100 (ou / usr / bin / sleep, dependendo da sua plataforma) ao loop de script e inicie o Bourne Shell. Depois de usar o ps (1) para obter os IDs do processo para o comando sleep e o bash que executa o script, chame
pgrp <pid>
e substitua "<pid>" pelo ID do processo do sleep e do bash que executa o script. Você verá diferentes IDs de grupos de processos. Agora chame algo comopgrp < /dev/pts/7
(substitua o nome tty pelo tty usado pelo script) para obter o grupo de processos tty atual. O grupo de processos TTY é igual ao grupo de processos do comando sleep.Para corrigir: use um shell diferente.
As fontes recentes da Bourne Shell estão no meu pacote de ferramentas inteligentes que você pode encontrar aqui:
http://sourceforge.net/projects/schilytools/files/
fonte
bash
é essa? O AFAIKbash
só faz isso se você passar a opção -m ou -i.bash -c 'ps -j; ps -j; ps -j'
)./bin/sh -ce
. Eu tive que adicionar uma solução alternativa feia parasmake
matar explicitamente o grupo de processos de um comando em execução no momento, a fim de permitir o^C
cancelamento de uma chamada em camadas. Você verificou se o bash alterou o grupo de processos a partir do ID do grupo de processos com o qual foi iniciado?ARGV0=sh bash -ce 'ps -j; ps -j; ps -j'
relata o mesmo pgid para ps e bash em todas as invocações de 3 ps. (ARGV0 = sh é umazsh
maneira de passar o argumento [0]).