Preciso evitar que SIGINT (Ctrl-C) se propague de um subshell para suas funções de shell pai no Zsh.
Aqui está um exemplo mínimo:
function sox-record {
local output="${1:-$(mktemp).wav}"
(
rec "${output}" trim 0 300 # Part of sox package
)
echo "${output}" # Need this to continue executing after Ctrl-C
}
function audio-postprocess {
local audio="$(sox-record)"
# Process the audio file...
echo "${audio}"
}
function audio-transcribe {
local audio="$(audio-postprocess)"
# Send to transcription service...
transcribe_audio "${audio}" # Never reached if Ctrl-C during recording
}
A solução alternativa atual requer capturar SIGINT em todos os níveis, o que leva a um código repetitivo e sujeito a erros:
function sox-record {
local output="${1:-$(mktemp).wav}"
setopt localtraps
trap '' INT
(
rec "${output}" trim 0 300
)
trap - INT
echo "${output}"
}
function audio-postprocess {
setopt localtraps
trap '' INT
local audio="$(sox-record)"
trap - INT
# Process the audio file...
echo "${audio}"
}
function audio-transcribe {
setopt localtraps
trap '' INT
local audio="$(audio-postprocess)"
trap - INT
# Send to transcription service...
transcribe_audio "${audio}"
}
Quando o usuário pressiona Ctrl-C para parar a gravação, eu quero: 1. Que o rec
subprocesso termine (funcionando) 2. Que as funções pai continuem executando (requer captura de SIGINT em cada chamador)
Eu sei que:
- SIGINT é enviado a todos os processos no grupo de processos em primeiro plano
- O uso
setsid
cria um novo grupo de processos, mas impede que os sinais cheguem ao filho - Adicionar
trap '' INT
o pai requer que todos os chamadores também capturem SIGINT para evitar propagação.
Existe uma maneira de isolar SIGINT apenas para o subshell sem exigir tratamento de sinal em todas as funções pai? Ou isso é fundamentalmente impossível devido a como os grupos de processos Unix e a propagação de sinal funcionam?
Dei uma olhada nesta pergunta e tentei isto:
function sox-record {
local output="${1:-$(mktemp).wav}"
zsh -mfc "rec "${output}" trim 0 300" </dev/null >&2 || true
echo "${output}"
}
Embora isso funcione quando eu apenas chamo sox-record
, quando eu chamo uma função pai como audio-postprocess
, Ctrl-C não faz nada. (E eu tenho que usar pkill
to kill rec
.)
function audio-postprocess {
local audio="$(sox-record)"
# Process the audio file...
echo "${audio}"
}
SIGINT não se propaga para os pais . Após
^C
, o kernel envia o SIGINT para o grupo de processos em primeiro plano do terminal.Quando esse script zsh for iniciado em um terminal, seu shell interativo terá criado um grupo de processos (um job) para ele, tornado-o o grupo de processos em primeiro plano do terminal e executado o script. Todos os processos iniciados por esse script, incluindo os subshells e o que está sendo executado,
rec
estarão nesse grupo e todos receberão SIGINT em^C
.Se você quiser que apenas o processo em execução
rec
receba esse sinal, ignore SIGINT globalmente no nível superior comtrap '' INT
e restaure-o somenterec
com(trap - INT; rec...)
.Você também pode mover o
trap
's para dentroaudio-transcribe
, mas precisa se lembrar de não executá-lo em um subshell, ou então ignorar o SIGINT só será aplicado àquele subshell e seus descendentes, não ao processo pai.Ter o script fazendo o controle de tarefas sozinho, ou seja, colocando-o
rec
em seu próprio grupo de processos e tornando-o o grupo de processos em primeiro plano, é uma abordagem que também pode funcionar, mas no zsh (pelo menos 5.9), você não pode fazer isso apenas com amonitor
opção (conforme definida porset -m
), pois em invocações não interativas, ele cria grupos de processos para comandos, mas não altera o grupo de processos em primeiro plano do terminal, então teria o efeito oposto ao que você deseja.Para
zsh
estar disposto a fazer o controle de trabalho terminal, você precisainteractive
(-i
) em vez disso.Nem
-i
nem-m
,ps
no mesmo grupo de processos que o pai e em primeiro plano.Sozinho
-m
,ps
está em um novo grupo de processos, mas não foi transformado no grupo de processos de primeiro plano do terminal (tpgid), então ele está em segundo plano e não receberá o SIGINT^C
.Com
-i
, o que implica-m
,ps
está em um novo grupo de processos que está em primeiro plano desta vez, enquantozsh
ele próprio está em segundo plano.Brincar com controle de trabalho em scripts é, no entanto, geralmente uma má ideia, pois é fonte de todos os tipos de comportamentos desagradáveis inesperados, então eu não faria isso. Se fizer isso, certifique-se de escrevê-lo:
Não incorpore a expansão no código zsh, pois isso a tornaria uma vulnerabilidade de injeção de comando .
$output