Buffer de saída do comando completamente antes de canalizar para outro comando?


10

Existe uma maneira de executar apenas um comando depois que outro é feito sem um arquivo temporário? Eu tenho um comando mais em execução e outro comando que formata a saída e a envia para um servidor HTTP usando curl. Se eu apenas executar commandA | commandB, commandBiniciará curl, conectará ao servidor e começará a enviar dados. Como commandAleva tanto tempo, o servidor HTTP expirará. Eu posso fazer o que eu quero comcommandA > /tmp/file && commandB </tmp/file && rm -f /tmp/file

Por curiosidade, quero saber se existe uma maneira de fazer isso sem o arquivo temporário. Eu tentei, mbuffer -m 20M -q -P 100mas o processo de enrolamento ainda é iniciado logo no início. O Mbuffer aguarda apenas o término do commandAenvio dos dados. (Os dados em si são apenas algumas centenas de kb no máximo)


Que tal commandA && commandB?
eyoung100

1
que não transmite a saída de commandApara commandB, transmite ?
Josef diz Restabelecer Monica

Inicia o Comando B se o Comando A for concluído com êxito, o que significaria que a curvatura não inicia mais cedo.
eyoung100

2
@ eyoung100 mas não passa stdout do commandA para o stdin para o commandB, que é o que Josef precisa!
roaima

Se ele quer que a saída seja passada, ele precisa usar um arquivo. Veja a resposta de cuonglm.
eyoung100

Respostas:


14

Isso é semelhante a algumas das outras respostas. Se você possui o pacote "moreutils", deve ter o spongecomando Tentar

commandA | sponge | { IFS= read -r x; { printf "%s\n" "$x"; cat; } | commandB; }

O spongecomando é basicamente um filtro de passagem (como cat), exceto que ele não começa a gravar a saída até ter lido toda a entrada. Ou seja, ele "absorve" os dados e os libera quando você os aperta (como uma esponja). Então, até certo ponto, isso é "trapaça" - se houver uma quantidade não trivial de dados, spongequase certamente usaremos um arquivo temporário. Mas é invisível para você; você não precisa se preocupar com tarefas domésticas, como escolher um nome de arquivo exclusivo e limpar depois.

A { IFS= read -r x; { printf "%s\n" "$x"; cat; } | commandB; } lê a primeira linha de saída do sponge. Lembre-se, isso não aparece até commandAterminar.  Em seguida, ele dispara commandB, grava a primeira linha no canal e chama catpara ler o restante da saída e gravá-lo no canal.


obrigado! O mesmo spongeacontece com o que eu usei mbuffer, mas parece ser mais adequado aqui. usar a leitura é inteligente aqui. Definitivamente vai se lembrar disso para o futuro.
Josef diz Restabelecer Monica

@ Josef: eu nunca ouvi falar mbufferantes; pode ser tão bom quanto sponge. Concordo que usar readé um truque inteligente. Eu não posso levar todo o crédito por isso; ele aparece nas respostas de tempos em tempos no Stack Exchange (U&L, Superusuário, Ask Ubuntu etc.). De fato, a resposta da roaima a essa pergunta é muito semelhante à minha, exceto que ela não usa sponge(ou algo equivalente); portanto, como mencionei em um comentário, não demora em iniciar o commandBquanto você precisa (no meu entendimento de seu problema).
G-Man diz 'Reinstate Monica'

O github.com/ildar-shaimordanov/perl-utils#sponge possui uma versão de script de "sponge" envolvida em uma função bash.
marinara

5

Os comandos na linha de tubulação são iniciados simultaneamente, você precisa armazenar a commandAsaída em algum lugar para usar posteriormente. Você pode evitar o arquivo temporário usando a variável:

output=$(command A; echo A)
printf '%s' "${output%%A}" | commandB

Qual é o objetivo da echo Asubstituição no processo?
Digital Trauma

3
@DigitalTrauma: Evita que a substituição de comandos retire novas linhas à direita.
cuonglm

Ah, eu vejo, sim - boa captura de um possível caso de canto
Digital Trauma

Percebi que minha resposta estava incorreta e, portanto, a apaguei. Eu acho que isso parece correto. +1
Trauma digital

Muito obrigado! Isso é ótimo e, especialmente, o eco A / %% A para manter a nova linha (mesmo que eu não exija isso)
Josef diz Reinstate Monica

1

Não conheço nenhum utilitário UNIX padrão que possa resolver esse problema. Uma opção seria usar awkpara acumular commandAsaída e liberá-la de commandBuma só vez, como

commandA  | awk '{x = x ORS $0}; END{printf "%s", x | "commandB"}'

Lembre-se de que isso pode exigir muita memória, pois awkestá construindo uma sequência a partir de sua entrada.


1
Isso retira a última nova linha. Supondo que o último caractere seja uma nova linha, você precisa de uma \nou outra ORSno final.
G-Man diz 'Reinstate Monica'

0

Você pode resolver o requisito com um pequeno script. Essa variante específica evita o arquivo temporário e o possível consumo de memória à custa de processos adicionais.

#!/bin/bash
#
IFS= read LINE

if test -n "$LINE"
then
    test -t 2 && echo "Starting $*" >&2
    (
        echo "$LINE"
        cat

    ) | "$@"
else
    exit 0
fi

Se você chamasse o script waituntil(e o tornasse executável, coloque-o no PATH, etc), usaria assim

commandA... | waituntil commandB...

Exemplo

( sleep 3 ; date ; id ) | waituntil nl

1
Tudo isso faz com que o atraso seja iniciado commandBaté commandAque a sua primeira linha de saída seja escrita . Provavelmente isso não é bom o suficiente.
G-Man diz 'Reinstate Monica'

@ G-Man, mas não sabemos. Eu gosto do seu sponge. Off para ler sobre isso agora.
roaima
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.