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 stderreinige commandin varkönnen Sie tun
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Danach haben Sie alles:
echo "command gives $? and stderr '$var'";
Wenn commandes 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 -nAliase "$ 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>&1leitet stderrzur Ausgabeerfassung weiter$(..)
1>&3leitet stdoutvon der Ausgabeerfassung $(..)zurück zu der "äußeren", stdoutdie in Dateideskriptor 3 gespeichert wurde. Beachten Sie, dass sich stderrimmer 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 commandnicht plötzlich ein unbekannter offener Dateideskriptor angezeigt wird. Beachten Sie, dass in der Außenhülle FD 3 noch geöffnet ist, es jedoch commandnicht angezeigt wird.
- Letzteres ist wichtig, da sich einige Programme gerne
lvmüber unerwartete Dateideskriptoren beschweren. Und lvmbeschwert 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>&1als Argument verstanden werden, 9gefolgt 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-fddü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 || returnDies 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,
bashsodass 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
evals auf sichere Weise verwendet werden. Wird normalerweise evalals 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)