Isso realmente parece um bug em ksh
.
O que eu suspeito é que em
x=$(file /tmp)
ksh
gera um novo processo para executar o comando file
e lê sua saída por meio de um canal, não aguardando sua finalização (como todos os shells modernos, incluindo as versões modernas do ksh), mas esse comando retorna assim que o EOF é atingido ao ler esse pipe.
Esse comportamento pode ser confirmado pela execução:
ksh -c 'x=$(exec sh -c "echo foo;exec >&-; sleep 10"); echo "$x"'
E verifique se ksh
retorna imediatamente após ter saído em foo
ou após 10 segundos.
Se for esse o caso, significa que o comando file
será finalizado e fará com que um SIGCLD seja enviado para seu pai (o shell), após o comando x=...
retornou.
O shell destina-se a lidar com esses SIGCLD para indagar sobre a morte de seu filho. Se o shell tiver um filho correndo em segundo plano, ele deve estar pronto para que a morte aconteça a qualquer momento. Esse sinal SIGCLD, assim como qualquer sinal não ignorado, faria com que uma chamada de sistema de bloqueio fosse interrompida . O shell deve estar pronto para que isso aconteça, bloqueando o sinal enquanto faz uma chamada do sistema que pode ser interrompida, ou reativando a chamada do sistema interrompida depois de ter manipulado o sinal.
Neste caso, parece que nada disso está acontecendo. Na maioria das vezes, a chamada do sistema write
executada pelo ksh sobre a execução imediata do echo
retorna, portanto, não há chance de ser interrompida, mas após o canal para o qual o stdout aponta está cheio, o sistema write
a chamada acaba bloqueando e é quando é interrompida pelo SIGCLD. E o ksh não tenta novamente qual é o bug.
Podemos ver o mesmo comportamento, mesmo no Linux, se corrermos
strace -e write ksh -c 'i=0; while [ "$i" -lt 2000 ]; do : &
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
i=$(($i+1)); done' | (sleep 3; wc)
Então vemos:
write(1, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 61) = ? ERESTARTSYS (To be restarted)
--- SIGCHLD (Child exited) @ 0 (0) ---
write(1, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 61...
É o mesmo, o comando :
de finalização faz com que a chamada do sistema write
de bloqueio seja interrompida, mas, desta vez, o write
é reativado.
Uma solução alternativa pode consistir em evitar substituições de comandos antes de uma chamada para um echo
integrado, ou certificar-se de que write
seja feito por um processo diferente daquele que obtém o SIGCLD, por exemplo executando echo
comando em um subshell:
(echo "012...")
EDITAR : Um olhar mais atento na saída truss
revela que é o rastreamento do segundo loop que deve ser executado em um processo separado daquele que está executando o outro loop, então não deveria estar recebendo o SIGCLD da morte dos comandos file
. Poderia ter um SIGCLD, a partir do término do subshell executando o primeiro loop.
Além disso, se, como o resultado do seu teste sugere, o ksh esperar pelos processos gerados para a substituição de comandos, então os sinais SIGCLD recebidos não podem ser explicados pela terminação assíncrona do comando file
.
O que parece mais provável é que o pipe externo fique cheio, mas não o pipe entre os dois loops while, o SIGCLD é recebido durante o bloqueio echo
no segundo loop e vem da terminação do primeiro loop. Portanto, uma solução mais eficiente seria executar o segundo loop em um subshell em vez de cada comando echo
.
while ...; done | (while ...;done)