Nehmen wir an, ich habe ein Skript wie das folgende:
nutzlos.sh
echo "This Is Error" 1>&2
echo "This Is Output"
Und ich habe ein anderes Shell-Skript:
auchUseless.sh
./useless.sh | sed 's/Output/Useless/'
Ich möchte "This Is Error" oder einen beliebigen anderen stderr von useless.sh in eine Variable einfangen. Nennen wir es FEHLER.
Beachten Sie, dass ich stdout für etwas verwende. Ich möchte stdout weiterhin verwenden, daher ist das Umleiten von stderr in stdout in diesem Fall nicht hilfreich.
Im Grunde möchte ich das tun
./useless.sh 2> $ERROR | ...
aber das funktioniert offensichtlich nicht.
Ich weiß auch, dass ich das könnte
./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`
aber das ist hässlich und unnötig.
Wenn hier keine Antworten auftauchen, muss ich das leider tun.
Ich hoffe, es gibt einen anderen Weg.
Hat jemand eine bessere Idee?
Es wäre netter, die Fehlerdatei so zu erfassen:
ERROR=$(</tmp/Error)
Die Shell erkennt dies und muss 'cat
' nicht ausführen, um die Daten abzurufen.
Die größere Frage ist schwer. Ich glaube nicht, dass es einen einfachen Weg gibt. Sie müssen die gesamte Pipeline in die Sub-Shell integrieren und schließlich die endgültige Standardausgabe an eine Datei senden, damit Sie die Fehler zur Standardausgabe umleiten können.
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
Beachten Sie, dass das Semikolon benötigt wird (in klassischen Shells - Bourne, Korn - sicherlich; wahrscheinlich auch in Bash). Der '{}
' führt eine E/A-Umleitung über die eingeschlossenen Befehle aus. Wie geschrieben, würde es auch Fehler von sed
erfassen.
WARNUNG: Formal ungeprüfter Code - Verwendung auf eigene Gefahr.
Auf diese Weise können Sie die Ausgabe Ihres useless.sh
-Skripts über einen Befehl wie sed
übergeben und die stderr
in einer Variablen mit dem Namen error
speichern. Das Ergebnis der Pipe wird an stdout
gesendet, um angezeigt oder in einen anderen Befehl geleitet zu werden.
Es werden einige zusätzliche Dateideskriptoren eingerichtet, um die dafür erforderlichen Umleitungen zu verwalten.
#!/bin/bash
exec 3>&1 4>&2 #set up extra file descriptors
error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )
echo "The message is \"${error}.\""
exec 3>&- 4>&- # release the extra file descriptors
Stderr an stdout, stdout an/dev/null umgeleitet und dann mit den Backticks oder $()
den umgeleiteten stderr erfassen
ERROR=$(./useless.sh 2>&1 >/dev/null)
Für diese Frage gibt es viele Duplikate, von denen viele ein etwas einfacheres Verwendungsszenario haben, in dem Sie nicht stderr und stdout und den Exit-Code gleichzeitig erfassen möchten.
if result=$(useless.sh 2>&1); then
stdout=$result
else
rc=$?
stderr=$result
fi
funktioniert für das allgemeine Szenario, in dem Sie entweder eine korrekte Ausgabe im Erfolgsfall oder eine Diagnosemeldung auf stderr im Fehlerfall erwarten.
Beachten Sie, dass die Steueranweisungen der Shell $?
bereits unter der Haube prüfen. also alles was aussieht
cmd
if [ $? -eq 0 ], then ...
ist nur eine ungeschickte, unidiomatische Art zu sagen
if cmd; then ...
# command receives its input from stdin.
# command sends its output to stdout.
exec 3>&1
stderr="$(command </dev/stdin 2>&1 1>&3)"
exitcode="${?}"
echo "STDERR: $stderr"
exit ${exitcode}
So habe ich es gemacht:
#
# $1 - name of the (global) variable where the contents of stderr will be stored
# $2 - command to be executed
#
captureStderr()
{
local tmpFile=$(mktemp)
$2 2> $tmpFile
eval "$1=$(< $tmpFile)"
rm $tmpFile
}
Anwendungsbeispiel:
captureStderr err "./useless.sh"
echo -$err-
Es tut verwendet eine temporäre Datei. Aber zumindest das hässliche Zeug ist in eine Funktion gehüllt.
Zum Wohle des Lesers ist dieses Rezept hier
Wenn Sie stderr
von einigen command
in var
fangen möchten, können Sie dies tun
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Danach haben Sie alles:
echo "command gives $? and stderr '$var'";
Wenn command
einfach ist (nicht so etwas wie a | b
), können Sie den inneren {}
weglassen:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Eingewickelt in eine einfach wiederverwendbare bash
- Funktion (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="$("[email protected]" 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 die Standardpunkte zu speichern{ command; }
(oder "$ @") führt dann den Befehl innerhalb der Ausgabe aus und erfasst $(..)
2>&1
leitet stderr
zur Ausgabe um, die $(..)
erfasst1>&3
leitet stdout
von der Ausgabeerfassung $(..)
zurück zu dem "äußeren" stdout
, der in Dateideskriptor 3 gespeichert wurde. Beachten Sie, dass stderr
weiterhin auf verweist Wobei FD 1 zuvor zeigte: Auf den Ausgang, der $(..)
aufzeichnet3>&-
schließt dann den Dateideskriptor 3, da dieser nicht mehr benötigt wird, so dass command
nicht plötzlich einen unbekannten offenen Dateideskriptor anzeigt. Beachten Sie, dass in der äußeren Shell noch FD 3 geöffnet ist, command
es jedoch nicht erkennt.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. Ausgenommen natürlich Dateideskriptor 1 (hier wäre die Umleitungslogik falsch, aber für Dateideskriptor 1 können Sie einfach wie gewohnt var=$(command)
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 Shells (aus den 1980er Jahren) 99>&1
möglicherweise als Argument 9
gefolgt von 9>&1
verstehen (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. Dies 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 '"[email protected]"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
Sicherheitshinweis: Die ersten 3 Argumente zu
catch-var-from-fd-by-fd
dürfen nicht von Dritten übernommen werden. Geben Sie sie immer explizit "statisch" an.Also nein-nein-nein
catch-var-from-fd-by-fd $var $fda $fdb $command
, mach das niemals!Wenn Sie zufällig eine Variable Variablenname ü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 jedoch, häufig auftretende Skriptfehler zu erkennen und zu vermeiden.
Anmerkungen:
catch-var-from-fd-by-fd var 2 3 cmd..
ist dasselbe wie catch-stderr var cmd..
shift || return
ist nur eine Möglichkeit, hässliche Fehler zu vermeiden, falls Sie vergessen, die richtige Anzahl von Argumenten anzugeben. Möglicherweise wäre das Beenden der Shell eine andere Möglichkeit (dies erschwert jedoch das Testen über die Befehlszeile).exec
nicht benötigt, aber dann wird es wirklich hässlich.bash
umgeschrieben werden, sodass local -n
nicht erforderlich ist. Dann können Sie jedoch keine lokalen Variablen verwenden und es wird extrem hässlich!eval
s auf sichere Weise verwendet werden. Normalerweise wird eval
als gefährlich angesehen. In diesem Fall ist es jedoch nicht schlimmer als die Verwendung von "[email protected]"
(um beliebige Befehle auszuführen). Bitte achten Sie jedoch darauf, dass Sie die genauen und korrekten Anführungszeichen wie hier angegeben verwenden (sonst wird es sehr sehr gefährlich).Dies ist ein interessantes Problem, auf das ich gehofft hatte, dass es eine elegante Lösung gab. Leider habe ich eine Lösung, die Mr. Leffler ähnelt, aber ich füge hinzu, dass Sie aus einer Bash-Funktion für eine bessere Lesbarkeit unbrauchbar machen können:
#!/bin/bash Funktion nutzlos { /tmp/useless.sh | sed 's/Output/Useless /' } ERROR = $ (unbrauchbar) echo $ ERROR
Alle anderen Arten der Ausgabeumleitung müssen durch eine temporäre Datei gesichert werden.
Erfassen und Drucken von Dateien
ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )
Nervenzusammenbruch
Sie können $()
verwenden, um stdout zu erfassen, Sie möchten jedoch stattdessen stderr erfassen. Sie tauschen also stdout und stderr. Verwenden von fd 3 als temporärer Speicher im Standard-Swap-Algorithmus.
Wenn Sie UND erfassen möchten, verwenden Sie tee
, um ein Duplikat zu erstellen. In diesem Fall wird die Ausgabe von tee
von $()
erfasst und nicht zur Konsole geleitet. Stderr (von tee
) wird jedoch weiterhin in die Konsole gehen. Daher verwenden wir tee
als zweite Ausgabe über die spezielle Datei /dev/fd/2
, da tee
ein erwartet Dateipfad statt fd-Nummer.
HINWEIS: Dies ist eine sehr große Anzahl von Weiterleitungen in einer einzigen Zeile, und die Reihenfolge ist wichtig. $()
greift das stdout von tee
am Ende der Pipeline und die Pipeline selbst leitet stdout von ./useless.sh
an das stdin von tee
, nachdem wir stdin und stdout gegen ./useless.sh
ausgetauscht haben.
stdout von ./useless.sh verwenden
Das OP sagte, er wollte immer noch stdout verwenden (nicht nur drucken), wie ./useless.sh | sed 's/Output/Useless/'
.
Kein Problem, bevor Sie stdout und stderr austauschen. Ich empfehle, es in eine Funktion oder Datei (auch-useless.sh) zu verschieben und diese anstelle von ./useless.sh in der Zeile oben aufzurufen.
Wenn Sie jedoch stdout UND stderr erfassen möchten, müssen Sie auf temporäre Dateien zurückgreifen, da $()
jeweils nur eine ausführt und eine Subshell erstellt, aus der Sie keine Variablen zurückgeben können.
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
Dieser Beitrag hat mir geholfen, eine ähnliche Lösung für meine eigenen Zwecke zu finden:
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
Solange unsere NACHRICHT keine leere Zeichenfolge ist, geben wir sie an andere Sachen weiter. Dies lässt uns wissen, ob unser format_logs.py mit einer Art Python-Ausnahme fehlgeschlagen ist.
Wenn Sie die Verwendung einer temporären Datei umgehen möchten, können Sie möglicherweise die Prozessersetzung verwenden. Ich habe es noch nicht ganz zum Laufen gebracht. Dies war mein erster Versuch:
$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'
Dann habe ich es versucht
$ ./useless.sh 2> >( ERROR=$( cat <() ) )
This Is Output
$ echo $ERROR # $ERROR is empty
Jedoch
$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error
Die Prozessersetzung ist also im Allgemeinen das Richtige ... Unglücklicherweise, wenn ich STDIN in >( )
mit etwas in $()
einwickle, um die Variable zu erfassen, verliere ich den Inhalt von $()
. Ich denke, das liegt daran, dass $()
einen Unterprozess startet, der keinen Zugriff mehr auf den Dateideskriptor in/dev/fd hat, der dem übergeordneten Prozess gehört.
Die Prozessersetzung hat mir die Fähigkeit erarbeitet, mit einem Datenstrom zu arbeiten, der nicht mehr in STDERR enthalten ist. Leider kann ich ihn leider nicht so manipulieren, wie ich möchte.
In zsh:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
STDERR kann mit etwas Umleitungsmagie erfasst werden:
$ { error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&3 ; } 2>&1); } 3>&1
lrwxrwxrwx 1 rZZt rZZt 7 Aug 22 15:44 /bin -> usr/bin/
$ echo $error
ls: cannot access '/XXXX': No such file or directory
Beachten Sie, dass das Piping von STDOUT des Befehls (hier ls
) im innersten {
}
erfolgt. Wenn Sie einen einfachen Befehl ausführen (z. B. keine Pipe), können Sie diese inneren Klammern entfernen.
Sie können keine Pipe außerhalb des Befehls ausführen, da Piping in bash
und zsh
eine Subshell erstellt und die Zuweisung zu der Variablen in der Subshell für die aktuelle Shell nicht verfügbar ist.
In bash
sollte man nicht davon ausgehen, dass der Dateideskriptor 3 nicht verwendet wird:
{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1;
exec {tmp}>&- # With this syntax the FD stays open
Beachten Sie, dass dies in zsh
nicht funktioniert.
Danke an diese Antwort für die allgemeine Idee.
Für error proofing Ihre Befehle:
execute [INVOKING-FUNCTION] [COMMAND]
execute () {
function="${1}"
command="${2}"
error=$(eval "${command}" 2>&1 >"/dev/null")
if [ ${?} -ne 0 ]; then
echo "${function}: ${error}"
exit 1
fi
}
Inspired in Lean Manufacturing:
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR
Wird herstellen:
This Is Output
-
This Is Error