Tubo semi-assíncrono

11

Suponha que eu tenha o seguinte canal:

a | b | c | d

Como posso esperar pela conclusão de c(ou b) em shou bash? Isso significa que o script dpode iniciar a qualquer momento (e não precisa ser esperado), mas requer saída completa cpara funcionar corretamente.

O caso de uso é um difftoolpara gitcomparar imagens. É chamado gite precisa processar sua entrada (a a | b | cparte) e exibir os resultados da comparação (a dparte). O chamador excluirá a entrada necessária para ae b. Isso significa que, antes de retornar do script, o processo c(ou b) deve terminar. Por outro lado, não posso esperar dporque isso significa que estou aguardando a entrada do usuário.

Eu sei que posso gravar os resultados de cem um arquivo temporário ou talvez usar um FIFO no bash. (Não tenho certeza se o FIFO ajudará.) É possível conseguir isso sem a necessidade de arquivos temporários sh?

EDITAR

Talvez fosse suficiente se eu pudesse descobrir o ID do processo c(ou b) de uma maneira confiável. Em seguida, todo o canal poderia ser iniciado de forma assíncrona e eu poderia esperar por um ID do processo. Algo ao longo das linhas de

wait $(a | b | { c & [print PID of c] ; } | d)

EDIT ^ 2

Encontrei uma solução, comentários (ou ainda melhores soluções) são bem-vindos.

krlmlr
fonte
Você quer dizer que deseja diniciar ca saída do processamento somente após a cconclusão? Você não deseja diniciar o processamento de cada linha de saída como ela vem?
terdon
@terdon: Não, dé livre para começar quando quiser, mas cprecisa terminar antes que eu possa seguir em frente.
krlmlr
Isso parece ser contraditório, se dpode começar quando quiser, o que exatamente você está esperando?
terdon
@terdon: Expandido para mostrar o caso de uso.
krlmlr
Se dnão usar a saída de, centão parece não fazer sentido fazer dparte do pipeline. Mas se dusar a entrada, ddeve trabalhar um pouco na entrada depois de ler tudo para que sua abordagem faça alguma diferença.
Hauke ​​Laging

Respostas:

6
a | b | { c; [notify];} | d

A notificação pode ser feita, por exemplo, por um sinal para um PID que foi passado em uma variável de ambiente ( kill -USR1 $EXTPID) ou criando um arquivo ( touch /path/to/file).

Outra ideia:

Você executa o próximo processo (aquele em que você pode iniciar o processo):

a | b | { c; exec >&-; nextprocess;} | d

ou

a | b | { c; exec >&-; nextprocess &} | d
Hauke ​​Laging
fonte
Obrigado. Porém, criar um arquivo temporário para a saída intermediária é mais detalhado e mais fácil de analisar (para um humano). Além disso, como evito a condição de corrida com o sinal? ... Eu esperava uma solução mais limpa, mas se esse é o caminho a seguir, que assim seja.
krlmlr
@krlmlr Que condição de corrida?
Hauke ​​Laging
notifypoderia ser executado antes de eu configurar a armadilha de sinal. Como posso ter certeza de não perder esse sinal?
krlmlr
@krlmlr Você interrompe o script que chama o pipeline até receber um sinal próprio que é enviado após a trapdefinição.
Hauke ​​Laging
Sua edição: o canal é chamado em um loop, sobre o qual eu não tenho controle. Infelizmente, { c; bg; }ou { c; exit 0; }parece não funcionar.
krlmlr
5

Se eu entendi sua pergunta corretamente, isso deve funcionar:

a | b | c | { (exec <&3 3<&-; d) &} 3<&0

(o truque do fd 3 é porque alguns shells (a maioria) redirecionam stdin para / dev / null com &).

Stéphane Chazelas
fonte
4

No bash, você pode usar uma substituição de processo . O comando na substituição do processo é executado de forma assíncrona, não é esperado.

a | b | c > >(d)
Gilles 'SO- parar de ser mau'
fonte
Obrigado. Isso é bashapenas, certo - sem shsuporte?
Krlmlr
@krlmlr Apenas Bash / zsh (e ksh93 com algumas modificações).
Gilles 'SO- stop be evil'
3

Isto é o que eu descobri por tentativa e erro, com a ajuda da contribuição de Hauke:

a | b | { c; kill -PIPE $$; } | d

Equivalentemente:

a | b | ( c; kill -PIPE $$; ) | d

(O último é mais explícito, porque {}será executado em um subshell de qualquer maneira, se estiver dentro de um pipe.)

Alguns outros sinais (incluindo QUIT, TERMe USR1) de trabalho, também, no entanto, neste caso, a descrição de que o sinal é mostrado no terminal.

Gostaria de saber se esta é a intenção original do PIPEsinal. De acordo com o manual :

SIGPIPE: O PIPEsinal é enviado para um processo quando ele tenta gravar em um canal sem um processo conectado à outra extremidade.

Isso significa que, quando eu envio artificialmente um sinal de pipe para o subshell, ele termina silenciosamente, deixando o consumidor final ( d) sozinho.

Isso funciona em ambos she bash.

krlmlr
fonte
0

Você pode usar o spongeprograma no pacote "moreutils":

a | b | c | sponge | d

a esponja irá para o final da cprodução antes de ser canalizada d. Espero que seja o que você queria.

Minijackson
fonte
0

Você pode apenas fazer:

a | b | c | (d ; cat > /dev/null)

Assim, quando dterminar, catele absorverá o restante da cprodução até que termine.

Está bem. Após os comentários, acho que a resposta é começar diretamente dem segundo plano.

Faz:

a | b | c | (d &)

ou use a solução de Stephane Chazelas se houver problemas com a dleitura do stdin .

angus
fonte
Receio que meu caso de uso seja o contrário: ctermina antes de tenho que esperar até cterminar, mas não me importo d.
krlmlr
Desculpe, eu não entendi. O que você deseja fazer quando cterminar e dainda estiver em execução? Matar d?
angus
dpossui uma GUI e pode executar até que o usuário a feche.
krlmlr
OK ... mas isso já acontece. Quando a, be cterminam o trabalho, eles fecham o stdout e saem; e só dcontinua sendo executado. Deseja enviar dpara o plano de fundo quando cterminar? É isso?
angus
Sim, ddeve ser enviado para o plano de fundo assim que cterminar.
krlmlr