Os dois comandos de subshell assíncronos podem gravar com segurança em um stdout compartilhado?

4

O stdout pode ser sobrescrito por dois comandos subshell bourne (ou bash, se isso importa) executados de forma assíncrona?

(tail -f ./file1 & tail -f ./file2) | cat

Eu não me importo com a ordem da linha, apenas que cada linha de saída é composta por exatamente uma linha de entrada. Estou preocupado que algumas linhas possam ser parcialmente sobrescritas ou intercaladas.

Eu testei isso executando quatro comandos, cada um com uma linha única de 15 milhões de vezes cada. Parece funcionar, mas eu meio que esperava que falhasse.

Alguém pode explicar como isso não quebra? Cada subshell é armazenado em buffer e apenas uma subshell consegue gravar em stdout por vez? ou como isso é gerenciado.

Existe uma maneira melhor de fazer isso?

(Não importa que eu esteja usando tail nos subshells acima para fins ilustrativos . Eu realmente quero executar dois outros comandos que geram continuamente uma linha em o tempo para stdout.)

    
por Aeyoun 18.08.2017 / 15:29

2 respostas

4
As conchas têm pouco envolvimento lá. Tudo o que eles fazem é criar o pipe e iniciar os 3 comandos que são executados em paralelo independentemente do shell.

O que importa aqui é que os dois comandos tail escrevem para um descritor de arquivo para a mesma extremidade de escrita do mesmo pipe.

Se você fizer isso:

printf foo1 >> file1; sleep 1
printf foo2 >> file2; sleep 1
printf 'bar1\n' >> file1; sleep 1
printf 'bar2\n' >> file2

Você verá:

foo1foo2bar1
bar2

Porque é assim que eles foram escritos. Você deve certificar-se de que seus comandos geram uma linha completa por vez, e que essas linhas sejam menores que PIPE_BUF (4096 bytes no Linux) para que o write () seja atômico ( ele também pode escrever mais de uma linha por vez, desde que estejam todos cheios e seu tamanho cumulativo seja menor que PIPE_BUF).

Com o GNU grep , você pode fazer isso canalizando seus comandos para grep --line-buffered '^'

(tail -f ./file1 | grep --line-buffered '^' &
 tail -f ./file2 | grep --line-buffered '^') | cat

Isso garantiria que você recebesse uma chamada de sistema write() para cada linha da saída de ambos os comandos (nos casos em que os comandos não terminassem sua última linha de saída, grep adicionaria a nova linha ausente)

    
por 18.08.2017 / 15:49
0

1. Solução ruim

Na configuração padrão, stderr é não em buffer, mas stdout é.

Assim, a solução mais simples do seu problema é

  1. provê que as ferramentas usadas escrevam tudo linha por linha
  2. redireciona sua saída para o stderr ( >&2 )

No entanto, isso não funciona, porque esse buffer acontece na biblioteca C, dentro do processo. Se você redirecionar sua stdout para o stderr, também este stderr será unbuffered.

2. Melhor solução

Canalize sua saída em processo, o que lê tudo, e depois os escreve por linha. A maneira mais fácil seria

tool1 | while read; do echo "$REPLY"; done & tool2 | while read; do echo "$REPLY"; done

Para as execuções paralelas "belas" de múltiplos comandos / scripts, aqui você pode ler minha outra resposta.

3. A solução real

Infelizmente, como as saídas do processo são principalmente armazenadas em buffer pela libc, ou seja, são suas coisas internas, como elas mapeiam suas saídas para write(1, ...) chamadas no nível do kernel. Depende deles. Sem alterá-los, não temos controle sobre sua saída, o que eles ainda não escreveram.

No caso de a escrita acontecer com o mecanismo FILE* da libc, o setbuf (3) e as funções do fflush (3) podem ser seu amigo.

    
por 18.08.2017 / 15:43