script - bash tutorial




Bash: Erfasst die Ausgabe des im Hintergrund ausgeführten Befehls (2)

Bash hat tatsächlich eine Funktion namens Prozesssubstitution , um dies zu erreichen.

$ echo <(yes)
/dev/fd/63

Hier wird der Ausdruck <(yes) durch den Pfadnamen einer (Pseudo-Device-) Datei ersetzt, die mit der Standardausgabe eines asynchronen Jobs yes (der den String y in einer Endlosschleife ausgibt).

Versuchen wir nun, daraus zu lesen:

$ cat /dev/fd/63
cat: /dev/fd/63: No such file or directory

Das Problem hierbei ist, dass der yes Prozess in der Zwischenzeit beendet wurde, weil er ein SIGPIPE erhalten hat (er hatte keine Leser auf stdout).

Die Lösung ist das folgende Konstrukt

$ exec 3< <(yes)  # Save stdout of the 'yes' job as (input) fd 3.

Dies öffnet die Datei als Eingabe fd 3, bevor der Hintergrundjob gestartet wird.

Sie können jetzt jederzeit vom Hintergrundjob lesen. Für ein dummes Beispiel

$ for i in 1 2 3; do read <&3 line; echo "$line"; done
y
y
y

Beachten Sie, dass dies eine etwas andere Semantik hat als das Schreiben des Hintergrundjobs in eine Laufwerksgesicherte Datei: Der Hintergrundjob wird blockiert, wenn der Puffer voll ist (Sie leeren den Puffer durch Lesen von fd). Im Gegensatz dazu blockiert das Schreiben in eine laufwerkunterstützte Datei nur, wenn die Festplatte nicht reagiert.

Die Prozesssubstitution ist keine POSIX sh-Funktion.

Hier ist ein kurzer Hack, um eine asynchrone Job-Festplatte zu unterstützen (fast), ohne ihr einen Dateinamen zuzuweisen:

$ yes > backingfile &  # Start job in background writing to a new file. Do also look at `mktemp(3)` and the `sh` option `set -o noclobber`
$ exec 3< backingfile  # open the file for reading in the current shell, as fd 3
$ rm backingfile       # remove the file. It will disappear from the filesystem, but there is still a reader and a writer attached to it which both can use it.

$ for i in 1 2 3; do read <&3 line; echo "$line"; done
y
y
y

Linux wurde kürzlich auch die Option O_TEMPFILE hinzugefügt, die solche Hacks ermöglicht, ohne dass die Datei überhaupt sichtbar ist. Ich weiß nicht, ob Bash es bereits unterstützt.

UPDATE :

@rthur, wenn Sie die gesamte Ausgabe von fd 3 erfassen möchten, dann verwenden

output=$(cat <&3)

Beachten Sie jedoch, dass Sie Binärdaten im Allgemeinen nicht erfassen können: Es ist nur eine definierte Operation, wenn die Ausgabe ein Text im POSIX-Sinn ist. Die Implementierungen, die ich kenne, filtern einfach alle NUL-Bytes aus. Außerdem gibt POSIX an, dass alle nachfolgenden Zeilenumbrüche entfernt werden müssen.

(Bitte beachten Sie auch, dass das Erfassen der Ausgabe zu OOM führt, wenn der Schreiber niemals aufhört ( yes hört niemals auf). Aber natürlich gilt dieses Problem auch beim read wenn das Zeilentrennzeichen niemals zusätzlich geschrieben wird)

Ich versuche, ein Bash-Skript zu schreiben, das die Ausgabe eines Befehls erhält, der im Hintergrund ausgeführt wird. Leider kann ich es nicht zum Laufen bringen, die Variable, der ich die Ausgabe zuweise, ist leer - wenn ich die Zuweisung durch einen Echo-Befehl ersetze, funktioniert alles wie erwartet.

#!/bin/bash

function test {
    echo "$1"
}

echo $(test "echo") &
wait

a=$(test "assignment") &
wait

echo $a

echo done

Dieser Code erzeugt die Ausgabe:

echo

done

Ändern der Zuordnung zu

a=`echo $(test "assignment") &`

funktioniert, aber es scheint, als sollte es einen besseren Weg geben, dies zu tun.


Eine Möglichkeit, die Ausgabe des Hintergrundbefehls zu erfassen, besteht darin, die Ausgabe in eine Datei umzuleiten und die Ausgabe aus der Datei zu erfassen, nachdem der Hintergrundprozess beendet wurde:

test "assignment" > /tmp/_out &
wait
a=$(</tmp/_out)




bash