Se eu chamar algum comando, por exemplo, echo
eu posso usar os resultados desse comando em vários outros comandos com tee
. Exemplo:
echo "Hello world!" | tee >(command1) >(command2) >(command3)
Com o gato eu posso coletar os resultados de vários comandos. Exemplo:
cat <(command1) <(command2) <(command3)
Gostaria de poder fazer as duas coisas ao mesmo tempo, para poder tee
chamar esses comandos na saída de outra coisa (por exemplo, a echo
que escrevi) e depois coletar todos os resultados em uma única saída com cat
.
É importante manter os resultados em ordem, isto significa que as linhas na saída command1
, command2
e command3
não devem ser interligados, mas ordenou que os comandos são (como acontece com cat
).
Pode haver opções melhores do que cat
e, tee
mas essas são as que eu conheço até agora.
Quero evitar o uso de arquivos temporários porque o tamanho da entrada e da saída pode ser grande.
Como eu pude fazer isso?
PD: outro problema é que isso acontece em um loop, o que dificulta o manuseio de arquivos temporários. Este é o código atual que eu tenho e funciona para pequenos casos de teste, mas cria loops infinitos ao ler e escrever no auxfile de alguma forma que não entendo.
somefunction()
{
if [ $1 -eq 1 ]
then
echo "Hello world!"
else
somefunction $(( $1 - 1 )) > auxfile
cat <(command1 < auxfile) \
<(command2 < auxfile) \
<(command3 < auxfile)
fi
}
Leituras e escritos em auxfile parecem se sobrepor, fazendo com que tudo exploda.
fonte
echo HelloWorld > file; (command1<file;command2<file;command3<file)
ou para a saídaecho | tee cmd1 cmd2 cmd3; cat cmd1-output cmd2-output cmd3-output
. É assim que funciona - o tee pode dividir a entrada apenas se todos os comandos funcionarem e processarem em paralelo. se se dorme de comando (porque você não quer intercalação) ele vai simplesmente bloquear todos os comandos, de modo a evitar o preenchimento de memória com entrada ...Respostas:
Você pode usar uma combinação do GNU stdbuf e
pee
do moreutils :fazer xixi
popen(3)
essas 3 linhas de comando do shell e depoisfread
a entrada efwrite
nas três, que serão armazenadas em buffer até 1M.A idéia é ter um buffer pelo menos tão grande quanto a entrada. Dessa maneira, mesmo que os três comandos sejam iniciados ao mesmo tempo, eles verão apenas a entrada de entrada quando os três comandos forem
pee
pclose
seqüenciais.Em cada um
pclose
,pee
libera o buffer para o comando e aguarda sua finalização. Isso garante que, enquanto aquelescmdx
comandos não comecem a produzir nada antes de receberem qualquer entrada (e não bifurcem um processo que possa continuar produzindo após o retorno do pai), a saída dos três comandos não será intercalado.Na verdade, é como usar um arquivo temporário na memória, com a desvantagem de os três comandos serem iniciados simultaneamente.
Para evitar iniciar os comandos simultaneamente, você pode escrever
pee
como uma função shell:Mas tome cuidado para que outras conchas
zsh
falhem na entrada binária com caracteres NUL.Isso evita o uso de arquivos temporários, mas isso significa que toda a entrada é armazenada na memória.
De qualquer forma, você precisará armazenar a entrada em algum lugar, na memória ou em um arquivo temporário.
Na verdade, é uma pergunta bastante interessante, pois mostra o limite da ideia do Unix de ter várias ferramentas simples cooperando para uma única tarefa.
Aqui, gostaríamos de ter várias ferramentas para cooperar com a tarefa:
echo
)tee
)cmd1
,cmd2
,cmd3
)cat
).Seria bom se todos pudessem rodar juntos ao mesmo tempo e trabalhar duro com os dados que devem processar assim que estiverem disponíveis.
No caso de um comando de filtro, é fácil:
Todos os comandos são executados simultaneamente,
cmd1
começa a coletar dadossrc
assim que estiverem disponíveis.Agora, com três comandos de filtro, ainda podemos fazer o mesmo: iniciá-los simultaneamente e conectá-los aos tubos:
O que podemos fazer com relativa facilidade com pipes nomeados :
(acima disso
} 3<&0
é para contornar o fato de&
redirecionarstdin
de/dev/null
e usamos<>
para evitar a abertura dos tubos para bloquear até a outra extremidade (cat
) também seja aberta)Ou, para evitar pipes nomeados, um pouco mais dolorosamente com o
zsh
coproc:Agora, a pergunta é: quando todos os programas forem iniciados e conectados, os dados fluirão?
Temos duas restrições:
tee
alimenta todas as suas saídas na mesma taxa, para que ele possa enviar dados apenas na taxa do tubo de saída mais lento.cat
só começará a ler a partir do segundo tubo (tubo 6 no desenho acima) quando todos os dados tiverem sido lidos no primeiro (5).O que isso significa é que os dados não fluirão no tubo 6 até a
cmd1
conclusão. E, como no casotr b B
acima, isso pode significar que os dados também não fluirão no tubo 3, o que significa que não fluirão em nenhum dos tubos 2, 3 ou 4, pois sãotee
alimentados na taxa mais lenta de todos os 3.Na prática, esses canais têm um tamanho não nulo; portanto, alguns dados conseguirão passar e, pelo menos no meu sistema, posso fazê-lo funcionar até:
Além disso, com
Temos um impasse, onde estamos nessa situação:
Enchemos os tubos 3 e 6 (64 kiB cada).
tee
leu que byte extra, ele alimentou-acmd1
, mascmd2
esvaziá-locmd2
não pode esvaziá-lo porque está bloqueado na escrita no tubo 6, esperandocat
esvaziá-locat
não pode esvaziá-lo porque está aguardando até que não haja mais entrada no canal 5.cmd1
não posso dizer quecat
não há mais entrada porque ela está esperando por mais entradastee
.tee
não posso dizer quecmd1
não há mais entrada porque está bloqueada ... e assim por diante.Temos um loop de dependência e, portanto, um impasse.
Agora, qual é a solução? Tubos maiores 3 e 4 (grandes o suficiente para conter toda
src
a produção) seriam suficientes . Podemos fazer isso, por exemplo, inserindopv -qB 1G
entretee
ecmd2/3
onde podemospv
armazenar até 1 G de dados aguardandocmd2
ecmd3
lendo-os. Isso significaria duas coisas:cmd2
, na realidade, só começaria a processar dados quando o cmd1 terminasse.Uma solução para o segundo problema seria aumentar também os tubos 6 e 7. Supondo que
cmd2
ecmd3
produzindo tanto quanto eles consomem, isso não consumiria mais memória.A única maneira de evitar a duplicação dos dados (no primeiro problema) seria implementar a retenção de dados no próprio despachante, ou seja, implementar uma variação
tee
que possa alimentar os dados na taxa da saída mais rápida (mantendo os dados para alimentar o mais lentos no seu próprio ritmo). Não é realmente trivial.Portanto, no final, o melhor que podemos obter razoavelmente sem programação é provavelmente algo como (sintaxe Zsh):
fonte
+1
para a arte agradável ASCII :-)O que você propõe não pode ser feito facilmente com nenhum comando existente e, de qualquer maneira, não faz muito sentido. A idéia de tubos (
|
em Unix / Linux) é quecmd1 | cmd2
nacmd1
saída de gravações (no máximo) até que um buffer de memória preenchimentos, e, em seguida,cmd2
executa a leitura de dados a partir do buffer (no máximo) até que ele está vazio. Ou seja,cmd1
e écmd2
executado ao mesmo tempo, nunca é necessário ter mais do que uma quantidade limitada de dados "em voo" entre eles. Se você deseja conectar várias entradas a uma única saída, se um dos leitores ficar atrás dos outros, você interrompe os outros (qual é o objetivo de executar em paralelo, então?) Ou esconde a saída que o retardatário ainda não leu (qual é o sentido de não ter um arquivo intermediário?).Nos meus quase 30 anos de experiência no Unix, não me lembro de nenhuma situação que realmente se beneficiasse com um canal de saída múltipla.
Hoje, você pode combinar várias saídas em um fluxo, mas não de maneira intercalada (como as saídas
cmd1
ecmd2
devem ser intercaladas? Uma linha por vez? Se revezam escrevendo 10 bytes? "Parágrafos" alternativos definidos de alguma forma? E se apenas não escrever algo por um longo tempo - tudo isso é complexo de lidar). É feito através, por exemplo(cmd1; cmd2; cmd3) | cmd4
, os programascmd1
,cmd2
ecmd3
são executados um após o outro, a saída é enviada como entrada paracmd4
.fonte
Para o seu problema de sobreposição, no Linux (e com
bash
ouzsh
nãoksh93
), você pode fazer o seguinte:Observe o uso de, em
(...)
vez de,{...}
para obter um novo processo a cada iteração, para que possamos ter um novo fd 3 apontando para um novoauxfile
.< /dev/fd/3
é um truque para acessar o arquivo excluído agora. Ele não funcionará em sistemas diferentes do Linux onde< /dev/fd/3
é semelhantedup2(3, 0)
e, portanto, o fd 0 seria aberto no modo somente gravação com o cursor no final do arquivo.Para evitar a bifurcação da função aninhada, você pode escrevê-la como:
O shell cuidaria de fazer backup do fd 3 em cada iteração. Você acabaria ficando sem descritores de arquivo mais cedo.
Embora você ache mais eficiente fazê-lo como:
Ou seja, não aninhe os redirecionamentos.
fonte