Befehlsausgabe vollständig puffern, bevor an einen anderen Befehl weitergeleitet wird?

10

Gibt es eine Möglichkeit, einen Befehl erst auszuführen, nachdem ein anderer ohne temporäre Datei ausgeführt wurde? Ich habe einen länger laufenden Befehl und einen anderen Befehl, der die Ausgabe formatiert und sie mit curl an einen HTTP-Server sendet. Wenn ich nur ausführen commandA | commandB, commandBwird starten curl, eine Verbindung zum Server herstellen und Daten senden. Da dies commandAso lange dauert, tritt beim HTTP-Server eine Zeitüberschreitung auf. Ich kann machen was ich willcommandA > /tmp/file && commandB </tmp/file && rm -f /tmp/file

Aus Neugier möchte ich wissen, ob es eine Möglichkeit gibt, dies ohne die temporäre Datei zu tun. Ich habe es versucht, mbuffer -m 20M -q -P 100aber der Curl-Prozess wird noch am Anfang gestartet. Mbuffer wartet nur, bis commandAdie Daten tatsächlich gesendet wurden . (Die Daten selbst sind maximal einige hundert kb groß)

Josef sagt Reinstate Monica
quelle
Was ist mit commandA && commandB?
eyoung100
1
das überträgt nicht die Ausgabe von commandAan commandB, oder?
Josef sagt Reinstate Monica
Es startet Befehl B, wenn Befehl A erfolgreich abgeschlossen wurde, was bedeuten würde, dass die Locke nicht früh beginnt.
eyoung100
2
@ eyoung100, aber es wird nicht stdout von commandA an stdin für commandB übergeben, was Josef braucht!
Roaima
Wenn die Ausgabe übergeben werden soll, muss er eine Datei verwenden. Siehe cuonglms Antwort.
eyoung100

Antworten:

14

Dies ähnelt einigen anderen Antworten. Wenn Sie das Paket "moreutils" haben, sollten Sie den spongeBefehl haben. Versuchen

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

Der spongeBefehl ist im Grunde ein Durchgangsfilter (wie cat), außer dass er erst mit dem Schreiben der Ausgabe beginnt, wenn er die gesamte Eingabe gelesen hat. Das heißt, es "saugt" die Daten auf und gibt sie dann frei, wenn Sie sie zusammendrücken (wie ein Schwamm). Bis zu einem gewissen Grad handelt es sich also um „Betrug“. Wenn eine nicht triviale Datenmenge vorhanden ist, wird mit spongeziemlicher Sicherheit eine temporäre Datei verwendet. Aber es ist für dich unsichtbar; Sie müssen sich nicht um die Haushaltsführung kümmern, z. B. um die Auswahl eines eindeutigen Dateinamens und die anschließende Bereinigung.

Der { IFS= read -r x; { printf "%s\n" "$x"; cat; } | commandB; } liest die erste Ausgabezeile von sponge. Denken Sie daran, dass dies erst angezeigt wird, wenn commandAes abgeschlossen ist.  Dann wird es gestartet commandB, schreibt die erste Zeile in die Pipe und ruft catauf, um den Rest der Ausgabe zu lesen und in die Pipe zu schreiben.

G-Man sagt "Reinstate Monica"
quelle
Vielen Dank! Das spongegleiche, wofür ich verwendet habe mbuffer, scheint hier aber besser geeignet zu sein. Lesen ist hier klug. Ich werde mich auf jeden Fall für die Zukunft daran erinnern.
Josef sagt Reinstate Monica
@ Josef: Ich habe noch nie davon gehört mbuffer; es könnte tatsächlich genauso gut sein wie sponge. Ich bin damit einverstanden, dass die Verwendung readein kluger Trick ist. Ich kann es nicht voll anerkennen; Es wird von Zeit zu Zeit in Antworten in Stack Exchange (U & L, Super User, Ask Ubuntu usw.) angezeigt. Tatsächlich ist Roaimas Antwort auf diese Frage meiner sehr ähnlich, außer dass sie nicht verwendet wird sponge(oder etwas Äquivalentes). Wie ich in einem Kommentar erwähnt habe, verzögert sie den Start nicht commandBso sehr, wie Sie es benötigen (nach meinem Verständnis von Ihr Problem).
G-Man sagt 'Reinstate Monica'
github.com/ildar-shaimordanov/perl-utils#sponge enthält eine Skriptversion von "sponge", die in eine Bash-Funktion eingeschlossen ist.
Marinara
4

Befehle in der Pipeline werden gleichzeitig gestartet. Sie müssen die commandAAusgabe an einem Ort speichern , um sie später verwenden zu können. Sie können temporäre Dateien vermeiden, indem Sie die Variable verwenden:

output=$(command A; echo A)
printf '%s' "${output%%A}" | commandB
cuonglm
quelle
Was ist der Zweck der echo ASubstitution im Prozess?
Digitales Trauma
3
@DigitalTrauma: Verhindert, dass durch das Ersetzen von Befehlen nachgestellte Zeilenumbrüche entfernt werden.
Cuonglm
Ah, ich verstehe, ja - guter Fang eines möglichen Eckfalls
Digital Trauma
Ich stellte fest, dass meine Antwort falsch war, und löschte sie. Ich denke, das sieht stattdessen richtig aus. +1
Digitales Trauma
Vielen Dank! Das ist großartig und besonders das Echo A / %% A, um die neue Zeile zu halten (auch wenn ich das nicht benötige)
Josef sagt Reinstate Monica
1

Ich kenne kein Standard-UNIX-Dienstprogramm, das dieses Problem beheben kann. Eine Möglichkeit wäre die Verwendung sein awkzu akkumulieren commandAAusgang und spülen Sie es commandBbei einem Schuss, wie so

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

Beachten Sie, dass dies speicherintensiv sein kann, da awkaus seiner Eingabe eine Zeichenfolge aufgebaut wird.

iruvar
quelle
1
Dadurch wird die letzte neue Zeile entfernt. Angenommen, das letzte Zeichen ist eine neue Zeile, benötigen Sie am Ende die eine \noder andere ORS.
G-Man sagt 'Reinstate Monica'
0

Sie können die Anforderung mit einem kleinen Skript lösen. Diese spezielle Variante vermeidet die temporäre Datei und potenzielle Speicherprobleme auf Kosten zusätzlicher Prozesse.

#!/bin/bash
#
IFS= read LINE

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

    ) | "$@"
else
    exit 0
fi

Wenn Sie das Skript aufrufen waituntil(und es ausführbar machen, in das PATHusw. einfügen), würden Sie es so verwenden

commandA... | waituntil commandB...

Beispiel

( sleep 3 ; date ; id ) | waituntil nl
Roaima
quelle
1
Dies tut , ist alle Verzögerung beginnen , commandBbis commandAgeschrieben hat seine erste Linie der Ausgabe . Das ist wahrscheinlich nicht gut genug.
G-Man sagt "Reinstate Monica"
@ G-Man aber wir wissen es nicht. Ich mag deine sponge. Jetzt können Sie sich darüber informieren.
Roaima