Zum Nutzen des Lesers dieses Rezept hier
- kann als Oneliner wiederverwendet werden, um stderr in eine Variable zu fangen
- gibt weiterhin Zugriff auf den Rückkehrcode des Befehls
- Opfert einen temporären Dateideskriptor 3 (der natürlich von Ihnen geändert werden kann)
- Und setzt diese temporären Dateideskriptoren nicht dem inneren Befehl aus
Wenn Sie fangen wollen stderr
einige command
in var
können Sie tun
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Danach haben Sie alles:
echo "command gives $? and stderr '$var'";
Wenn command
es einfach ist (nicht so ähnlich a | b
), können Sie das Innere {}
weglassen:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Eingewickelt in eine einfach wiederverwendbare Funktion bash
(benötigt wahrscheinlich Version 3 und höher für local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Erklärt:
local -n
Aliase "$ 1" (das ist die Variable für catch-stderr
)
3>&1
verwendet den Dateideskriptor 3, um dort Standardpunkte zu speichern
{ command; }
(oder "$ @") führt dann den Befehl innerhalb der Ausgabeerfassung aus $(..)
- Bitte beachten Sie, dass die genaue Reihenfolge hier wichtig ist (wenn Sie die Dateideskriptoren falsch machen, werden die Dateideskriptoren falsch gemischt):
2>&1
leitet stderr
zur Ausgabeerfassung weiter$(..)
1>&3
leitet stdout
von der Ausgabeerfassung $(..)
zurück zu der "äußeren", stdout
die in Dateideskriptor 3 gespeichert wurde. Beachten Sie, dass sich stderr
immer noch darauf bezieht, wo FD 1 zuvor gezeigt hat: Zur Ausgabeerfassung$(..)
3>&-
schließt dann den Dateideskriptor 3, da er nicht mehr benötigt wird, so dass command
nicht plötzlich ein unbekannter offener Dateideskriptor angezeigt wird. Beachten Sie, dass in der Außenhülle FD 3 noch geöffnet ist, es jedoch command
nicht angezeigt wird.
- Letzteres ist wichtig, da sich einige Programme gerne
lvm
über unerwartete Dateideskriptoren beschweren. Und lvm
beschwert sich bei stderr
- genau das, was wir einfangen werden!
Sie können mit diesem Rezept jeden anderen Dateideskriptor abfangen, wenn Sie ihn entsprechend anpassen. Außer Dateideskriptor 1 natürlich (hier wäre die Umleitungslogik falsch, aber für Dateideskriptor 1 können Sie einfach var=$(command)
wie gewohnt verwenden).
Beachten Sie, dass dies den Dateideskriptor 3 opfert. Wenn Sie diesen Dateideskriptor benötigen, können Sie die Nummer jederzeit ändern. Beachten Sie jedoch, dass einige Muscheln (aus den 1980er Jahren) möglicherweise 99>&1
als Argument verstanden werden, 9
gefolgt von 9>&1
(dies ist kein Problem für bash
).
Beachten Sie auch, dass es nicht besonders einfach ist, diesen FD 3 über eine Variable konfigurierbar zu machen. Das macht die Dinge sehr unlesbar:
: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
Sicherheitshinweis: Die ersten 3 Argumente catch-var-from-fd-by-fd
dürfen nicht von Dritten stammen. Geben Sie sie immer explizit "statisch".
Also nein-nein-nein catch-var-from-fd-by-fd $var $fda $fdb $command
, mach das niemals!
Wenn Sie zufällig einen variablen Variablennamen übergeben, gehen Sie mindestens wie folgt vor:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
Dies schützt Sie immer noch nicht vor jedem Exploit, hilft aber zumindest dabei, häufige Skriptfehler zu erkennen und zu vermeiden.
Anmerkungen:
catch-var-from-fd-by-fd var 2 3 cmd..
ist das gleiche wie catch-stderr var cmd..
shift || return
Dies ist nur eine Möglichkeit, hässliche Fehler zu vermeiden, falls Sie vergessen, die richtige Anzahl von Argumenten anzugeben. Vielleicht wäre das Beenden der Shell eine andere Möglichkeit (dies erschwert jedoch das Testen über die Befehlszeile).
- Die Routine wurde so geschrieben, dass sie leichter zu verstehen ist. Man kann die Funktion so umschreiben, dass sie nicht benötigt wird
exec
, aber dann wird sie wirklich hässlich.
- Diese Routine kann auch für Nicht-Routine umgeschrieben werden,
bash
sodass keine Notwendigkeit besteht local -n
. Dann können Sie jedoch keine lokalen Variablen verwenden und es wird extrem hässlich!
- Beachten Sie auch, dass die
eval
s auf sichere Weise verwendet werden. Wird normalerweise eval
als gefährlich angesehen. In diesem Fall ist es jedoch nicht böser als die Verwendung "$@"
(zum Ausführen beliebiger Befehle). Bitte achten Sie jedoch darauf, das genaue und korrekte Zitat wie hier gezeigt zu verwenden (sonst wird es sehr, sehr gefährlich ).
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)