A saída de substituição do processo está fora da ordem

12

O

echo one; echo two > >(cat); echo three; 
O comando

fornece resultados inesperados.

Eu li isso: Como a substituição do processo é implementada no bash? e muitos outros artigos sobre a substituição do processo no internet, mas não entendo porque se comporta dessa maneira.

Resultado esperado:

one
two
three

Saída real:

prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two

Além disso, esses dois comandos devem ser equivalentes do meu ponto de vista, mas não o fazem:

##### first command - the pipe is used.
prompt$ seq 1 5 | cat
1
2
3
4
5
##### second command - the process substitution and redirection are used.
prompt$ seq 1 5 > >(cat)
prompt$ 1
2
3
4
5

Por que eu acho que eles deveriam ser o mesmo? Porque, ambos conectam a saída seq à entrada cat através do canal anônimo - Wikipedia, substituição de processos .

Pergunta: Por que se comporta dessa maneira? Onde está o meu erro? A resposta abrangente é desejada (com explicação de como o bash faz isso sob o capô).

    
por MiniMax 10.11.2017 / 17:47

2 respostas

16

Sim, em bash como em ksh (de onde vem o recurso), os processos dentro da substituição do processo não são esperados (antes de executar o próximo comando no script).

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.

Isso foi corrigido em zsh , que aguarda cmd2 lá (mas não se você o escrever como cmd1 > >(cmd2) e cmd1 não está embutido, use {cmd1} > >(cmd2) como documented ).

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 10.11.2017 / 17:59
2

Você pode canalizar o segundo comando para outro cat , que aguardará até que seu tubo de entrada seja fechado. Ex:

prompt$ echo one; echo two > >(cat) | cat; echo three;
one
two
three
prompt$

Curto e simples.

==========

Tão simples quanto parece, muita coisa acontece nos bastidores. Você pode ignorar o restante da resposta se não estiver interessado em saber como isso funciona.

Quando você tem echo two > >(cat); echo three , >(cat) é bifurcado pelo shell interativo e é executado independentemente de echo two . Assim, echo two termina e, em seguida, echo three é executado, mas antes que o >(cat) seja concluído. Quando bash obtém dados de >(cat) quando não esperava (alguns milissegundos depois), isso lhe dá aquela situação de prompt, na qual você precisa acessar a nova linha para voltar ao terminal (Igual a outro usuário mesg 'ed você).

No entanto, considerando echo two > >(cat) | cat; echo three , duas sub-camadas são geradas (conforme a documentação do símbolo | ).

Um subshell chamado A é para echo two > >(cat) e um subshell chamado B é para cat . A é automaticamente conectado a B (stdout de A é o stdin de B). Então, echo two e >(cat) começam a executar. O stdout de >(cat) é definido como stdout de A, que é igual ao stdin de B. Após echo two terminar, A sai, fechando sua stdout. No entanto, >(cat) ainda está mantendo a referência ao stdin de B. O segundo stdin cat está segurando o stdin de B, e esse cat não sairá até ver um EOF. Um EOF é fornecido apenas quando ninguém mais tiver o arquivo aberto no modo de gravação, portanto, o stdout de >(cat) está bloqueando o segundo cat . B permanece esperando naquele segundo cat . Como echo two foi encerrado, >(cat) eventualmente recebe um EOF, então >(cat) libera seu buffer e sai. Ninguém está segurando o stdin de B's / second cat , então o segundo cat lê um EOF (B não está lendo o seu stdin, não importa). Esse EOF faz com que o segundo cat libere seu buffer, feche seu stdout e saia, e então B sai porque cat saiu e B estava esperando em cat .

Uma ressalva disso é que o bash também gera um subshell para >(cat) ! Por causa disso, você verá que

echo two > >(sleep 5) | cat; echo three

ainda esperará 5 segundos antes de executar echo three , mesmo que sleep 5 não esteja segurando o stdin de B. Isso ocorre porque uma subshell oculta C gerada por >(sleep 5) está aguardando sleep e C está mantendo a stdin de B. Você pode ver como

echo two > >(exec sleep 5) | cat; echo three

Não esperará no entanto, pois sleep não está segurando o stdin de B, e não há nenhuma sub-shell fantasma C que esteja mantendo stdin de B (exec forçará sleep a substituir C, ao invés de bicar e fazer C esperar sleep ). Independentemente desta advertência,

echo two > >(exec cat) | cat; echo three

ainda executará adequadamente as funções em ordem, conforme descrito anteriormente.

    
por 24.07.2018 / 21:38