¿Completamente la salida del comando buffer antes de pasar a otro comando?

10

¿Hay alguna manera de ejecutar solo un comando después de que otro se haga sin un archivo temporal? Tengo un comando más largo y otro comando que formatea el resultado y lo envía a un servidor HTTP usando curl. Si solo ejecuto commandA | commandB, commandBcomenzaré curl, me conectaré al servidor y comenzaré a enviar datos. Debido a que commandAlleva tanto tiempo, el servidor HTTP se agota. Puedo hacer lo que quiera concommandA > /tmp/file && commandB </tmp/file && rm -f /tmp/file

Por curiosidad, quiero saber si hay una manera de hacerlo sin el archivo temporal. Lo intenté mbuffer -m 20M -q -P 100pero el proceso de curl aún se inicia desde el principio. Mbuffer espera hasta que termine de commandAenviar los datos. (Los datos en sí son solo unos pocos cientos de kb como máximo)

Josef dice reinstalar a Mónica
fuente
¿Qué hay de commandA && commandB?
eyoung100
1
que no transmite la salida de commandAa commandB, ¿verdad?
Josef dice Reinstate Monica
Inicia el Comando B si el Comando A se completa con éxito, lo que significaría que el rizo no comienza temprano.
eyoung100
2
@ eyoung100 pero no pasa stdout del comando A al stdin para el comando B, que es lo que Josef necesita.
roaima
Si quiere que se pase la salida, tiene que usar un archivo. Ver la respuesta de cuonglm.
eyoung100

Respuestas:

14

Esto es similar a algunas de las otras respuestas. Si tiene el paquete "moreutils", debe tener el spongecomando. Tratar

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

El spongecomando es básicamente un filtro de paso (como cat) excepto que no comienza a escribir la salida hasta que haya leído toda la entrada. Es decir, "absorbe" los datos y luego los libera cuando los aprieta (como una esponja). Entonces, hasta cierto punto, esto es "trampa": si hay una cantidad de datos no trivial, spongecasi con certeza usa un archivo temporal. Pero es invisible para ti; no tiene que preocuparse por cosas de limpieza como elegir un nombre de archivo único y limpiar después.

El { IFS= read -r x; { printf "%s\n" "$x"; cat; } | commandB; } lee la primera línea de salida de sponge. Recuerde, esto no aparece hasta que commandAhaya terminado.  Luego se dispara commandB, escribe la primera línea en la tubería e invoca catpara leer el resto de la salida y escribirla en la tubería.

G-Man dice 'restablecer a Mónica'
fuente
¡Gracias! También spongelo que solía hacer mbuffer, pero parece ser más adecuado aquí. El uso de lectura es inteligente aquí. Definitivamente lo recordaré para el futuro.
Josef dice Reinstate Monica
@Josef: Nunca antes había escuchado mbuffer; en realidad podría ser tan bueno como sponge. Estoy de acuerdo en que usar reades un truco inteligente. No puedo tomar todo el crédito por ello; aparece de vez en cuando en las respuestas de Stack Exchange (U&L, Super User, Ask Ubuntu, etc.). De hecho, la respuesta de Roaima a esta pregunta es muy similar a la mía, excepto que no usa sponge(o algo equivalente), por lo que, como mencioné en un comentario, no demora el inicio commandBtanto como lo necesite (en mi entendimiento de tu problema).
G-Man dice 'Restablece a Monica' el
github.com/ildar-shaimordanov/perl-utils#sponge tiene una versión de script de "esponja" envuelta en una función bash.
Marinara
5

Los comandos en la tubería se inician simultáneamente, debe almacenar la commandAsalida en algún lugar para usarla más tarde. Puede evitar el archivo temporal utilizando la variable:

output=$(command A; echo A)
printf '%s' "${output%%A}" | commandB
Cuonglm
fuente
¿Cuál es el propósito de la echo Asustitución en el proceso?
Digital Trauma
3
@ DigitalTrauma: evita que la sustitución de comandos elimine las nuevas líneas finales.
Cuonglm
Ah, ya veo, sí, buena captura de un posible caso de esquina
Digital Trauma
Me di cuenta de que mi respuesta era incorrecta, así que la eliminé. Creo que esto parece correcto en su lugar. +1
Trauma digital
¡Muchas gracias! Eso es genial y, especialmente, el eco A / %% A para mantener la nueva línea (incluso si no lo necesito)
Josef dice Reinstate Monica
1

No conozco ninguna utilidad UNIX estándar que pueda solucionar este problema. Una opción sería usar awkpara acumular la commandAsalida y enjuagarla de commandBuna vez, así

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

Tenga en cuenta que esto podría requerir mucha memoria, ya que awkestá creando una cadena a partir de su entrada.

iruvar
fuente
1
Esto elimina la última línea nueva. Suponiendo que el último personaje es una nueva línea, necesita uno \nu otro ORSal final.
G-Man dice 'Restablece a Monica' el
0

Puede resolver el requisito con un pequeño script. Esta variante en particular evita el archivo temporal y el potencial consumo de memoria a expensas de procesos adicionales.

#!/bin/bash
#
IFS= read LINE

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

    ) | "$@"
else
    exit 0
fi

Si tuviera que llamar al script waituntil(y hacerlo ejecutable, ponerlo en el PATH, etc.), lo usaría así

commandA... | waituntil commandB...

Ejemplo

( sleep 3 ; date ; id ) | waituntil nl
roaima
fuente
1
Todo lo que hace es retrasar el inicio commandBhasta que commandAhaya escrito su primera línea de salida . Eso probablemente no sea lo suficientemente bueno.
G-Man dice 'Restablece a Mónica' el
@ G-Man pero no lo sabemos. Me gusta tu sponge. Fuera para leer sobre eso ahora.
roaima