bash espera pelo processo na substituição do processo, mesmo que o comando seja inválido [duplicado]

8

Estou tentando escrever um script de utilitário errpipe com uma API simples que executa stderr por meio de um filtro. No começo, tentei implementá-lo usando o recurso de substituição de processos do bash.

#!/bin/bash

com="$1"
errpipe="$2"

$com 2> >(1>&2 $errpipe)

O problema é que a saída parece estranha quando com não existe.

Se eu digitar

sh-3.2$ ./errpipe foo cat

Eu obtenho

sh-3.2$ ./errpipe foo cat
sh-3.2$ ./errpipe: line 6: foo: command not found
@

com @ representando o cursor. Em outras palavras, o prompt do shell foi impresso cedo demais. Eu suspeito que isso seja porque o shell script principal não está aguardando o processo de substituição do processo ser concluído. Jogar em wait no final do script não parece resolver o problema.

Estou aberto a uma solução que usa o recurso bash , ksh , zsh ou possivelmente um recurso louco awk . Eu acho que sei como conectar isso usando algo como C ou Perl, que expõe uma API mais rica para manipular processos e descritores de arquivo, mas eu gostaria de evitar usá-la a menos que não haja uma alternativa.

Uma solução que "quase funciona" é usar o fato de que $$ não é alterado quando o shell se bifurca e aciona um sinal no pai quando o errpipe é concluído.

#!/bin/bash

com="$1"
errpipe="$2"

$com 2> >(1>&2 $errpipe; kill -SIGUSR1 $$)

while true; do
    sleep 60
done

Isto corrige o problema original mas a) é feio e b) imprime User defined signal 1: 30 antes de terminar mesmo se eu tiver um manipulador de sinal para SIGUSR1 ec) fará um loop para sempre se o processo responsável por enviar um SIGUSR para o pai morrer de alguma forma .

    
por Gregory Nisbet 26.08.2017 / 20:40

1 resposta

10

Sim, em bash como em ksh (de onde vem o recurso), os processos dentro da substituição do processo não são esperados.

por <(...) , geralmente está bem como em:

cmd1 <(cmd2)

o shell estará aguardando por cmd1 e cmd1 estará normalmente aguardando cmd2 em virtude de ler até o final do arquivo no canal substituído e esse fim de arquivo normalmente acontece quando cmd2 morre. Essa é a mesma razão pela qual várias shells (não bash ) não se incomodam em esperar por cmd2 in cmd2 | cmd1 .

Por cmd1 >(cmd2) , no entanto, esse geralmente não é o caso, já que é mais cmd2 que normalmente espera por cmd1 , então geralmente sairá depois.

zsh esperará cmd2 (mas não se você escrever como cmd1 > >(cmd2) , use {cmd1} > >(cmd2) como documentado ).

ksh não espera por padrão, mas permite que você espere por ele com o wait incorporado (ele também disponibiliza o pid em $! , embora isso não ajude se você usar cmd1 >(cmd2) >(cmd3) )

rc (com a sintaxe cmd1 >{cmd2} ), igual a ksh , exceto que você pode obter os pids de todos os processos em segundo plano com $apids .

es (também com cmd1 >{cmd2} ) aguarda cmd2 como em zsh e também espera por cmd2 em <{cmd2} process redirections.

bash faz o pid de cmd2 (ou mais exatamente do subshell, já que executa cmd2 em um processo filho desse subshell, embora seja o último comando lá) disponível em $! , mas não deixa você esperar por isso.

Se você precisar usar bash , poderá solucionar o problema usando um comando que aguardará os dois comandos com:

{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1

Isso faz com que ambos cmd1 e cmd2 tenham seu fd 3 aberto para um pipe. cat aguardará o final do arquivo na outra extremidade, portanto, normalmente só sairá quando cmd1 e cmd2 estiverem inativos. E o shell aguardará esse comando cat . Você pode ver isso como uma rede para capturar o término de todos os processos em segundo plano (você pode usá-lo para outras coisas iniciados em segundo plano como & , coprocs ou até mesmo comandos que antecedem eles mesmos, se não fecharem todos os descritores de arquivo como daemons normalmente fazem).

Note que, graças ao processo de subshell desperdiçado mencionado acima, ele funciona mesmo se cmd2 fechar seu fd3 (os comandos geralmente não fazem isso, mas alguns como sudo ou ssh do). Versões futuras de bash podem eventualmente fazer a otimização como em outras shells. Então você precisaria de algo como:

{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1

Para ter certeza de que ainda há um processo de shell extra com o fd 3 aberto esperando pelo comando sudo .

Note que cat não lê nada (já que os processos não escrevem no fd 3). Está lá apenas para sincronização. Ele fará apenas uma chamada de sistema read() que retornará sem nada no final.

Você pode realmente evitar a execução de cat usando uma substituição de comando para fazer a sincronização do pipe:

{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1

Desta vez, é o shell em vez de cat que está lendo o canal cuja outra extremidade está aberta no fd 3 de cmd1 e cmd2 . Estamos usando uma atribuição de variável para que o status de saída de cmd1 esteja disponível em $? .

Ou você poderia fazer a substituição do processo manualmente, e então você poderia até mesmo usar o sh do seu sistema como se fosse a sintaxe padrão do shell:

{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1

no entanto observe, como observado anteriormente, que nem todas as implementações sh esperariam por cmd1 após cmd2 ter terminado (embora seja melhor do que o contrário). Nesse momento, $? contém o status de saída de cmd2 ; embora bash e zsh disponibilizem o status de saída de cmd1 em ${PIPESTATUS[0]} e $pipestatus[1] respectivamente (consulte também a opção pipefail em alguns shells, portanto $? pode relatar a falha de outros componentes do duto que o último)

Observe que yash tem problemas semelhantes com o recurso redirecionamento do processo. cmd1 >(cmd2) seria escrito cmd1 /dev/fd/3 3>(cmd2) lá. Mas cmd2 não está aguardando e você não pode usar wait para esperar por ele e seu pid também não está disponível na variável $! . Você usaria o mesmo trabalho em torno de bash .

    
por 26.08.2017 / 21:59