Ich denke, bevor man sagt "man kann nicht", sollten die Leute es zumindest mit ihren eigenen Händen versuchen ...
Einfache und saubere Lösung, ohne Verwendung evaloder etwas Exotisches
1. Eine minimale Version
{
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
} < <((printf '\0%s\0' "$(some_command)" 1>&2) 2>&1)
Benötigt: printf ,read
2. Ein einfacher Test
Ein Dummy-Skript zum Produzieren stdoutund stderr:useless.sh
#!/bin/bash
echo "This is stderr" 1>&2
echo "This is stdout"
Das eigentliche Skript, das erfasst stdoutund stderr:capture.sh
#!/bin/bash
{
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
} < <((printf '\0%s\0' "$(./useless.sh)" 1>&2) 2>&1)
echo 'Here is the captured stdout:'
echo "${CAPTURED_STDOUT}"
echo
echo 'And here is the captured stderr:'
echo "${CAPTURED_STDERR}"
echo
Ausgabe von capture.sh
Here is the captured stdout:
This is stdout
And here is the captured stderr:
This is stderr
3. Wie es funktioniert
Der Befehl
(printf '\0%s\0' "$(some_command)" 1>&2) 2>&1
sendet die Standardausgabe von some_commandto printf '\0%s\0'und erstellt so die Zeichenfolge \0${stdout}\n\0(wobei \0es sich um ein NULByte und \nein neues Zeilenzeichen handelt); Die Zeichenfolge \0${stdout}\n\0wird dann zu dem Standardfehler umgeleitet, bei dem der Standardfehler von some_commandbereits vorhanden war, wodurch die Zeichenfolge erstellt wird ${stderr}\n\0${stdout}\n\0, die dann zurück zur Standardausgabe umgeleitet wird.
Danach der Befehl
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
beginnt mit dem Lesen der Zeichenfolge ${stderr}\n\0${stdout}\n\0bis zum ersten NULByte und speichert den Inhalt in ${CAPTURED_STDERR}. Dann der Befehl
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
liest die gleiche Zeichenfolge bis zum nächsten NULByte weiter und speichert den Inhalt in ${CAPTURED_STDOUT}.
4. Machen Sie es unzerbrechlich
Die obige Lösung basiert auf einem NULByte für das Trennzeichen zwischen stderrund stdout, daher funktioniert es nicht, wenn aus irgendeinem Grund stderrandere NULBytes enthalten sind.
Obwohl dies niemals passieren sollte, ist es möglich, das Skript vollständig unzerbrechlich zu machen, indem alle möglichen NULBytes von stdoutund stderrvor der Übergabe beider Ausgaben an read(Desinfektion) entfernt werden - NULBytes würden ohnehin verloren gehen, da es nicht möglich ist, sie in Shell-Variablen zu speichern :
{
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
} < <((printf '\0%s\0' "$((some_command | tr -d '\0') 3>&1- 1>&2- 2>&3- | tr -d '\0')" 1>&2) 2>&1)
Benötigt: printf , read,tr
BEARBEITEN
Ich habe ein weiteres Beispiel für die Weitergabe des Exit-Status an die aktuelle Shell entfernt, da es, wie Andy in den Kommentaren ausgeführt hat, nicht so "unzerbrechlich" war, wie es sein sollte (da es nicht printfzum Puffern eines von verwendet wurde die Ströme). Für die Aufzeichnung füge ich den problematischen Code hier ein:
Beibehaltung des Exit-Status (immer noch unzerbrechlich)
Die folgende Variante überträgt auch den Exit-Status von some_commandan die aktuelle Shell:
{
IFS= read -r -d '' CAPTURED_STDOUT;
IFS= read -r -d '' CAPTURED_STDERR;
(IFS= read -r -d '' CAPTURED_EXIT; exit "${CAPTURED_EXIT}");
} < <((({ { some_command ; echo "${?}" 1>&3; } | tr -d '\0'; printf '\0'; } 2>&1- 1>&4- | tr -d '\0' 1>&4-) 3>&1- | xargs printf '\0%s\0' 1>&4-) 4>&1-)
Benötigt: printf , read, tr,xargs
Andy hat dann den folgenden "Änderungsvorschlag" zur Erfassung des Exit-Codes eingereicht:
Einfache und saubere Lösung, die den Ausgangswert spart
Wir können am Ende stderreine dritte Information hinzufügen , eine weitere NULplus den exitStatus des Befehls. Es wird nach, stderraber vorher ausgegebenstdout
{
IFS= read -r -d '' CAPTURED_STDERR;
IFS= read -r -d '' CAPTURED_EXIT;
IFS= read -r -d '' CAPTURED_STDOUT;
} < <((printf '\0%s\n\0' "$(some_command; printf '\0%d' "${?}" 1>&2)" 1>&2) 2>&1)
Seine Lösung scheint zu funktionieren, hat aber das kleine Problem, dass der Exit-Status als letztes Fragment der Zeichenfolge platziert werden sollte, damit wir exit "${CAPTURED_EXIT}"in runden Klammern starten und den globalen Bereich nicht verschmutzen können, wie ich es versucht hatte das entfernte Beispiel. Das andere Problem ist, dass wir , da die Ausgabe seines Innersten printfsofort an das stderrvon angehängt wird, some_commandmögliche NULBytes nicht mehr bereinigen können stderr, da unter diesen jetzt auch unser NUL Begrenzer ist.
Nachdem ich ein wenig über den ultimativen Ansatz nachgedacht habe, habe ich eine Lösung gefunden, bei printfder sowohl stdout der Exit-Code als auch der Exit-Code als zwei verschiedene Argumente zwischengespeichert werden, damit sie niemals stören.
Das erste, was ich tat, war eine Möglichkeit, den Exit-Status dem dritten Argument von mitzuteilen printf, und dies war in seiner einfachsten Form (dh ohne Desinfektion) sehr einfach zu tun.
5. Beibehaltung des Exit-Status - der Blaupause (ohne Desinfektion)
{
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
(IFS=$'\n' read -r -d '' _ERRNO_; exit ${_ERRNO_});
} < <((printf '\0%s\0%d\0' "$(some_command)" "${?}" 1>&2) 2>&1)
Benötigt: exit , printf,read
Die Dinge werden jedoch sehr chaotisch, wenn wir versuchen, Desinfektion einzuführen. Das Starten trzur Bereinigung der Streams überschreibt tatsächlich unseren vorherigen Exit-Status. Daher besteht die einzige Lösung anscheinend darin, diesen in einen separaten Deskriptor umzuleiten, bevor er verloren geht, ihn dort zu belassen, bis er trseine Aufgabe zweimal erledigt, und ihn dann wieder an seinen Platz umzuleiten .
Nach einigen ziemlich akrobatischen Umleitungen zwischen Dateideskriptoren kam ich zu diesem Ergebnis.
6. Beibehaltung des Ausgangsstatus durch Desinfektion - unzerbrechlich (umgeschrieben)
Der folgende Code ist eine Umschreibung des Beispiels, das ich entfernt habe. Außerdem werden mögliche NULBytes in den Streams bereinigt , sodass readdies immer ordnungsgemäß funktioniert.
{
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
(IFS=$'\n' read -r -d '' _ERRNO_; exit ${_ERRNO_});
} < <((printf '\0%s\0%d\0' "$(((({ some_command; echo "${?}" 1>&3-; } | tr -d '\0' 1>&4-) 4>&2- 2>&1- | tr -d '\0' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
Benötigt: exit , printf, read,tr
Diese Lösung ist wirklich robust. Der Exit-Code wird immer in einem anderen Deskriptor getrennt gehalten, bis er printfdirekt als separates Argument erreicht wird.
7. Die ultimative Lösung - eine Allzweckfunktion mit Exit-Status
Wir können den obigen Code auch in eine Allzweckfunktion umwandeln.
catch() {
{
IFS=$'\n' read -r -d '' "${1}";
IFS=$'\n' read -r -d '' "${2}";
(IFS=$'\n' read -r -d '' _ERRNO_; return ${_ERRNO_});
} < <((printf '\0%s\0%d\0' "$(((({ ${3}; echo "${?}" 1>&3-; } | tr -d '\0' 1>&4-) 4>&2- 2>&1- | tr -d '\0' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
}
Benötigt: cat , exit, printf, read,tr
Mit der catchFunktion können wir das folgende Snippet starten:
catch MY_STDOUT MY_STDERR './useless.sh'
echo "The \`./useless.sh\` program exited with code ${?}"
echo
echo 'Here is the captured stdout:'
echo "${MY_STDOUT}"
echo
echo 'And here is the captured stderr:'
echo "${MY_STDERR}"
echo
und erhalten Sie das folgende Ergebnis:
The `./useless.sh` program exited with code 0
Here is the captured stdout:
This is stderr 1
This is stderr 2
And here is the captured stderr:
This is stdout 1
This is stdout 2
8. Was passiert in den letzten Beispielen?
Hier folgt eine schnelle Schematisierung:
some_commandwird gestartet: Wir haben dann some_command's stdoutauf dem Deskriptor 1, some_command' s stderrauf dem Deskriptor 2 und some_command's Exit-Code, der auf den Deskriptor 3 umgeleitet wird
stdoutwird an tr(Desinfektion) weitergeleitet
stderrwird mit stdout(vorübergehend unter Verwendung des Deskriptors 4) ausgetauscht und an tr(Desinfektion) weitergeleitet
- Der Exit-Code (Deskriptor 3) wird gegen
stderr(jetzt Deskriptor 1) ausgetauscht und an weitergeleitetexit $(cat)
stderr (jetzt Deskriptor 3) wird zum Deskriptor 1 umgeleitet, Ende erweitert als zweites Argument von printf
- Der Exit-Code von
exit $(cat)wird durch das dritte Argument von erfasstprintf
- Die Ausgabe von
printfwird an den Deskriptor 2 umgeleitet, wo stdoutbereits vorhanden war
- Die Verkettung
stdoutund Ausgabe von printfwird an weitergeleitetread
9. Die POSIX-kompatible Version 1 (zerbrechlich)
Prozesssubstitutionen (die < <()Syntax) sind kein POSIX-Standard (obwohl dies de facto der Fall ist). In einer Shell, die die < <()Syntax nicht unterstützt , können Sie das gleiche Ergebnis nur über die <<EOF … EOFSyntax erzielen . Leider können wir keine NULBytes als Trennzeichen verwenden, da diese vor dem Erreichen automatisch entfernt werden read. Wir müssen ein anderes Trennzeichen verwenden. Die natürliche Wahl fällt auf das CTRL+ZZeichen (ASCII-Zeichen Nr. 26). Hier ist eine zerbrechliche Version (Ausgaben dürfen niemals das CTRL+ZZeichen enthalten , sonst werden sie gemischt).
_CTRL_Z_=$'\cZ'
{
IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" CAPTURED_STDERR;
IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" CAPTURED_STDOUT;
(IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" _ERRNO_; exit ${_ERRNO_});
} <<EOF
$((printf "${_CTRL_Z_}%s${_CTRL_Z_}%d${_CTRL_Z_}" "$(some_command)" "${?}" 1>&2) 2>&1)
EOF
Benötigt: exit , printf,read
10. Die POSIX-kompatible Version 2 (unzerbrechlich, aber nicht so gut wie die Nicht-POSIX-Version)
Und hier ist seine unzerbrechliche Version, direkt in Funktionsform (wenn eines stdoutoder stderrmehrere CTRL+ZZeichen enthält , wird der Stream abgeschnitten, aber niemals mit einem anderen Deskriptor ausgetauscht).
_CTRL_Z_=$'\cZ'
catch_posix() {
{
IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" "${1}";
IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" "${2}";
(IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" _ERRNO_; return ${_ERRNO_});
} <<EOF
$((printf "${_CTRL_Z_}%s${_CTRL_Z_}%d${_CTRL_Z_}" "$(((({ ${3}; echo "${?}" 1>&3-; } | cut -z -d"${_CTRL_Z_}" -f1 | tr -d '\0' 1>&4-) 4>&2- 2>&1- | cut -z -d"${_CTRL_Z_}" -f1 | tr -d '\0' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
EOF
}
Benötigt: cat , cut, exit, printf, read,tr