it-swarm.com.de

Erfassen Sie stdout und stderr in verschiedenen Variablen

Ist es möglich, stdout und stderr in verschiedene Variablen zu speichern oder zu erfassen, ohne eine temporäre Datei zu verwenden? Im Moment mache ich das, um stdout in out und stderr in err zu erhalten, wenn some_command Ausgeführt wird, aber ich möchte die temporäre Datei vermeiden.

error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< error_file)
rm $error_file
59
ntc2

Ok, es wurde ein bisschen hässlich, aber hier ist eine Lösung:

unset t_std t_err
eval "$( (echo std; echo err >&2) \
        2> >(readarray -t t_err; typeset -p t_err) \
         > >(readarray -t t_std; typeset -p t_std) )"

dabei muss (echo std; echo err >&2) durch den tatsächlichen Befehl ersetzt werden. Die Ausgabe von stdout wird zeilenweise im Array $t_std Gespeichert, wobei die Zeilenumbrüche (-t) Und stderr in $t_err.

Wenn Sie keine Arrays mögen, können Sie dies tun

unset t_std t_err
eval "$( (echo std; echo err >&2 ) \
        2> >(t_err=$(cat); typeset -p t_err) \
         > >(t_std=$(cat); typeset -p t_std) )"

was das Verhalten von var=$(cmd) imitiert, außer dem Wert von $?, der uns zur letzten Änderung führt:

unset t_std t_err t_ret
eval "$( (echo std; echo err >&2; exit 2 ) \
        2> >(t_err=$(cat); typeset -p t_err) \
         > >(t_std=$(cat); typeset -p t_std); t_ret=$?; typeset -p t_ret )"

Hier wird $? In $t_ret Gespeichert

Getestet unter Debian Wheezy mit GNU bash, Version 4.2.37 (1) -release (i486-pc-linux- gnu).

36
TheConstructor

Jonathan hat die Antwort . Als Referenz ist dies der Trick ksh93. (erfordert eine nicht alte Version).

function out {
    echo stdout
    echo stderr >&2
}

x=${ { y=$(out); } 2>&1; }
typeset -p x y # Show the values

produziert

x=stderr
y=stdout

Das ${ cmds;} Syntax ist nur eine Befehlsersetzung, die keine Subshell erstellt. Die Befehle werden in der aktuellen Shell-Umgebung ausgeführt. Der Raum am Anfang ist wichtig ({ ist ein reserviertes Wort).

Stderr der inneren Befehlsgruppe wird nach stdout umgeleitet (damit es für die innere Ersetzung gilt). Als nächstes wird der Standardwert von outy zugewiesen, und der umgeleitete Standardwert wird von x erfasst, ohne den üblichen Verlust von y durch eine Befehlsersetzung Unterschale.

In anderen Shells ist dies nicht möglich, da bei allen Konstrukten, die die Ausgabe erfassen, der Produzent in eine Subshell versetzt werden muss, die in diesem Fall die Zuweisung enthalten würde.

update: Wird jetzt auch von mksh unterstützt.

15
ormaaj

Dieser Befehl legt die Werte stdout (stdval) und stderr (errval) in der aktuell ausgeführten Shell fest:

eval "$( execcommand 2> >(setval errval) > >(setval stdval); )"

vorausgesetzt, diese Funktion wurde definiert:

function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }

Ändern Sie den Befehl execcommand in den erfassten Befehl, sei es "ls", "cp", "df" usw.


All dies basiert auf der Idee, dass wir alle erfassten Werte mit Hilfe der Funktion setval in eine Textzeile konvertieren könnten. Dann wird setval verwendet, um jeden Wert in dieser Struktur zu erfassen:

execcommand 2> CaptureErr > CaptureOut

Konvertieren Sie jeden Erfassungswert in einen Setval-Aufruf:

execcommand 2> >(setval errval) > >(setval stdval)

Wickeln Sie alles in einen Execute-Aufruf und wiederholen Sie ihn:

echo "$( execcommand 2> >(setval errval) > >(setval stdval) )"

Sie erhalten die Deklarationsaufrufe, die jedes Setval erzeugt:

declare -- stdval="I'm std"
declare -- errval="I'm err"

Verwenden Sie eval, um diesen Code auszuführen (und die Variablen festzulegen):

eval "$( execcommand 2> >(setval errval) > >(setval stdval) )"

und schliesslich die eingestellten Variablen wiedergeben:

echo "std out is : |$stdval| std err is : |$errval|

Es ist auch möglich, den Rückgabewert (Exit) einzubeziehen.
Ein vollständiges Bash-Skript-Beispiel sieht folgendermaßen aus:

#!/bin/bash --

# The only function to declare:
function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }

# a dummy function with some example values:
function dummy { echo "I'm std"; echo "I'm err" >&2; return 34; }

# Running a command to capture all values
#      change execcommand to dummy or any other command to test.
eval "$( dummy 2> >(setval errval) > >(setval stdval); <<<"$?" setval retval; )"

echo "std out is : |$stdval| std err is : |$errval| return val is : |$retval|"
14
user2350426

Dies dient zum Abfangen von stdout und stderr in verschiedenen Variablen. Wenn Sie nur stderr fangen möchten und stdout unverändert lassen möchten, gibt es eine bessere und kürzere Lösung .

Um Summe alles hoch zum Wohle des Lesers, hier ist ein

Einfache wiederverwendbare Lösung bash

Diese Version verwendet Subshells und läuft ohne tempfile s. (Für eine Version tempfile, die ohne Unterschalen läuft, siehe meine andere Antwort .)

: catch STDOUT STDERR cmd args..
catch()
{
eval "$({
__2="$(
  { __1="$("${@:3}")"; } 2>&1;
  ret=$?;
  printf '%q=%q\n' "$1" "$__1" >&2;
  exit $ret
  )"
ret="$?";
printf '%s=%q\n' "$2" "$__2" >&2;
printf '( exit %q )' "$ret" >&2;
} 2>&1 )";
}

Beispiel Verwendung:

dummy()
{
echo "$3" >&2
echo "$2" >&1
return "$1"
}

catch stdout stderr dummy 3 $'\ndiffcult\n data \n\n\n' $'\nother\n difficult \n  data  \n\n'

printf 'ret=%q\n' "$?"
printf 'stdout=%q\n' "$stdout"
printf 'stderr=%q\n' "$stderr"

das druckt

ret=3
stdout=$'\ndiffcult\n data '
stderr=$'\nother\n difficult \n  data  '

Es kann also verwendet werden, ohne weiter darüber nachzudenken. Stellen Sie einfach catch VAR1 VAR2 Vor einen beliebigen command args.. Und schon sind Sie fertig.

Einige if cmd args..; then Werden zu if catch VAR1 VAR2 cmd args..; then. Wirklich nichts komplexes.

Diskussion

F: Wie funktioniert es?

Es fasst nur die Ideen der anderen Antworten in einer Funktion zusammen, sodass sie leicht wiederverwendet werden können.

catch() verwendet grundsätzlich eval, um die beiden Variablen zu setzen. Dies ähnelt https://stackoverflow.com/a/18086548

Betrachten Sie einen Aufruf von catch out err dummy 1 2a 3b:

  • lassen Sie uns zunächst den eval "$({ und den __2="$( überspringen. Ich werde später darauf zurückkommen.

  • __1="$("$("${@:3}")"; } 2>&1; führt dummy 1 2 3 aus und speichert es stdout in __1 zur späteren Verwendung. Also wird __1 Zu 2a. Es leitet auch stderr von dummy zu stdout um, sodass der äußere Fang stdout sammeln kann.

  • ret=$?; Fängt den Exit-Code ab, der 1 Ist.

  • printf '%q=%q\n' "$1" "$__1" >&2; Gibt dann out=2a An stderr aus. stderr wird hier verwendet, da das aktuelle stdout bereits die Rolle von stderr des Befehls dummy übernommen hat.

  • exit $ret Leitet dann den Beendigungscode (1) Zur nächsten Stufe weiter.

Nun zum äußeren __2="$( ... )":

  • Dies fängt stdout des obigen, welches das stderr des dummy - Aufrufs ist, in die Variable __2. (Wir könnten hier __1 Wiederverwenden, aber ich habe __2 Verwendet, um es weniger verwirrend zu machen.) So wird __2 Zu 3b

  • ret="$?"; Fängt den (zurückgegebenen) Rückkehrcode 1 (Von dummy) erneut ab

  • printf '%s=%q\n' "$2" "$__2" >&2; Gibt dann err=3a An stderr aus. stderr wird erneut verwendet, da bereits die andere Variable out=2a Ausgegeben wurde.

  • printf '( exit %q )' "$ret" >&2; then outputs the code to set the proper return value. I did not find a better way, as assignig it to a variable needs a variable name, which then cannot be used as first oder second argument to catch`.

Bitte beachten Sie, dass wir diese 2 printf als Optimierung auch als eine einzige wie printf '%s=%q\n( exit %q ) "$ __ 2" "$ ret" `hätten schreiben können.

Was haben wir bisher?

Wir haben folgendes an stderr geschrieben:

out=2a
err=3b
( exit 1 )

wobei out von $1 stammt, 2a von stdout von dummy stammt, err von $2, 3b Stammt aus stderr von dummy, und der 1 Stammt aus dem Rückkehrcode von dummy.

Bitte beachten Sie, dass %q Im Format printf für die Anführungszeichen sorgt, sodass die Shell die richtigen (einzelnen) Argumente für eval sieht. 2a Und 3b Sind so einfach, dass sie buchstäblich kopiert werden.

Nun zum äußeren eval "$({ ... } 2>&1 )";:

Dies führt alle obigen Befehle aus, die die 2 Variablen und die exit ausgeben, sie abfangen (dafür die 2>&1) Und mit eval in die aktuelle Shell analysieren.

Auf diese Weise werden die 2 Variablen gesetzt und auch der Rückgabecode.

F: Es wird eval verwendet, was böse ist. Also ist es sicher?

  • Solange printf %q Keine Bugs hat, sollte es sicher sein. Aber man muss immer sehr vorsichtig sein, man denke nur an Shellshock.

F: Bugs?

  • Es sind keine offensichtlichen Fehler bekannt, außer den folgenden:

    • Das Abfangen einer großen Ausgabe erfordert viel Arbeitsspeicher und CPU, da alles in Variablen abgelegt wird und von der Shell geparst werden muss. Also nutze es mit Bedacht.
    • Wie üblich verschluckt $(echo $'\n\n\n\n') alle Zeilenvorschübe , nicht nur den letzten. Dies ist eine POSIX-Anforderung. Wenn Sie die LFs unversehrt lassen möchten, fügen Sie einfach ein abschließendes Zeichen zur Ausgabe hinzu und entfernen Sie es anschließend wie im folgenden Rezept beschrieben (siehe abschließendes x, mit dem ein Softlink gelesen werden kann, der auf eine Datei verweist, die auf a endet $'\n'):

      target="$(readlink -e "$file")x"
      target="${target%x}"
      
    • Shell-Variablen können das Byte NUL ($'\0') Nicht enthalten. Sie werden einfach ignoriert, wenn sie zufällig in stdout oder stderr auftreten.

  • Der angegebene Befehl wird in einer Sub-Subshell ausgeführt. Es hat also keinen Zugriff auf $PPID Und kann auch keine Shell-Variablen ändern. Sie können eine Shell-Funktion catch, auch eingebaute, aber diese können Shell-Variablen nicht ändern (da dies nicht für alle in $( .. ) ausgeführten Funktionen möglich ist). Wenn Sie also eine Funktion in der aktuellen Shell ausführen und das Verzeichnis stderr/stdout abrufen müssen, müssen Sie dies auf die übliche Weise mit tempfile s tun. (Es gibt Möglichkeiten, dies so zu tun, dass das Unterbrechen der Shell normalerweise keine Trümmer hinterlässt, aber dies ist komplex und verdient seine eigene Antwort.)

F: Bash-Version?

  • Ich denke, Sie brauchen Bash 4 und höher (aufgrund von printf %q)

F: Das sieht immer noch so umständlich aus.

  • Richtig. Eine andere Antwort hier zeigt, wie es in ksh viel sauberer gemacht werden kann. Allerdings bin ich nicht an ksh gewöhnt, daher überlasse ich es anderen, ein ähnliches, einfach wiederzuverwendendes Rezept für ksh zu erstellen.

F: Warum verwenden Sie dann nicht ksh?

  • Weil dies eine bash Lösung ist

F: Das Skript kann verbessert werden

  • Natürlich können Sie einige Bytes auspressen und kleinere oder unverständlichere Lösungen erstellen. TU es einfach ;)

F: Es gibt einen Tippfehler. : catch STDOUT STDERR cmd args.. Soll # catch STDOUT STDERR cmd args.. Lesen

  • Eigentlich ist das so gemeint. : Wird in bash -x Angezeigt, während Kommentare unbemerkt verschluckt werden. So können Sie sehen, wo sich der Parser befindet, wenn Sie zufällig einen Tippfehler in der Funktionsdefinition haben. Es ist ein alter Debugging-Trick. Aber Vorsicht, Sie können mit den Argumenten von : Leicht einige nette Nebeneffekte erzeugen.

Bearbeiten: Ein paar weitere ; Hinzugefügt, um das Erstellen eines Single-Liner aus catch() zu vereinfachen. Und Abschnitt hinzugefügt, wie es funktioniert.

12
Tino

Named Pipes sind technisch gesehen keine temporären Dateien und werden hier von niemandem erwähnt. Sie speichern nichts im Dateisystem und Sie können sie löschen, sobald Sie sie verbinden (so dass Sie sie niemals sehen werden):

#!/bin/bash -e

foo () {
    echo stdout1
    echo stderr1 >&2
    sleep 1
    echo stdout2
    echo stderr2 >&2
}

rm -f stdout stderr
mkfifo stdout stderr
foo >stdout 2>stderr &             # blocks until reader is connected
exec {fdout}<stdout {fderr}<stderr # unblocks `foo &`
rm stdout stderr                   # filesystem objects are no longer needed

stdout=$(cat <&$fdout)
stderr=$(cat <&$fderr)

echo $stdout
echo $stderr

exec {fdout}<&- {fderr}<&- # free file descriptors, optional

Auf diese Weise können Sie mehrere Hintergrundprozesse ausführen und ihre Standard- und Standardeinträge zu einem geeigneten Zeitpunkt usw. asynchron erfassen.

Wenn Sie dies nur für einen Prozess benötigen, können Sie anstelle der Syntax {fdout}/{fderr} (Die eine freie fd für Sie findet) auch fest codierte fd-Nummern wie 3 und 4 verwenden.

9
Irfy

Hat die Auswertung nicht gefallen, daher hier eine Lösung, die einige Umleitungstricks verwendet, um die Programmausgabe in einer Variablen zu erfassen und diese Variable dann zu analysieren, um die verschiedenen Komponenten zu extrahieren. Das Flag -w legt die Blockgröße fest und beeinflusst die Reihenfolge der Standard-/Fehlernachrichten im Zwischenformat. 1 bietet eine potenziell hohe Auflösung zu Lasten des Overheads.

#######                                                                                                                                                                                                                          
# runs "[email protected]" and outputs both stdout and stderr on stdin, both in a prefixed format allowing both std in and out to be separately stored in variables later.                                                                  
# limitations: Bash does not allow null to be returned from subshells, limiting the usefullness of applying this function to commands with null in the output.                                                                   
# example:                                                                                                                                                                                                                       
#  var=$(keepBoth ls . notHere)                                                                                                                                                                                                  
#  echo ls had the exit code "$(extractOne r "$var")"                                                                                                                                                                            
#  echo ls had the stdErr of "$(extractOne e "$var")"                                                                                                                                                                            
#  echo ls had the stdOut of "$(extractOne o "$var")"                                                                                                                                                                            
keepBoth() {                                                                                                                                                                                                                     
  (                                                                                                                                                                                                                              
    prefix(){                                                                                                                                                                                                                    
      ( set -o pipefail                                                                                                                                                                                                          
        base64 -w 1 - | (                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
          while read c                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
          do echo -E "$1" "$c"                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
          done                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
        )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
      )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
    }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    ( (                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
        "[email protected]" | prefix o >&3                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
        echo  ${PIPESTATUS[0]} | prefix r >&3                                                                                                                                                                                                                                                                                                                                                                                                                                                           
      ) 2>&1 | prefix e >&1                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
    ) 3>&1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
  )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

extractOne() { # extract                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
  echo "$2" | grep "^$1" | cut --delimiter=' ' --fields=2 | base64 --decode -                                                                                                                                                                                                                                                                                                                                                                                                                           
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
3
mncl

Kurz gesagt, ich glaube, die Antwort lautet "Nein". Die Funktion capturing $( ... ) erfasst nur die Standardausgabe für die Variable. Es gibt keine Möglichkeit, den Standardfehler in einer separaten Variablen zu erfassen. Also, was Sie haben, ist ungefähr so ​​ordentlich wie es nur geht.

2

Was ist mit ... = D

GET_STDERR=""
GET_STDOUT=""
get_stderr_stdout() {
    GET_STDERR=""
    GET_STDOUT=""
    unset t_std t_err
    eval "$( (eval $1) 2> >(t_err=$(cat); typeset -p t_err) > >(t_std=$(cat); typeset -p t_std) )"
    GET_STDERR=$t_err
    GET_STDOUT=$t_std
}

get_stderr_stdout "command"
echo "$GET_STDERR"
echo "$GET_STDOUT"
2
Eduardo Lucio

Für den Leser ist hier eine Lösung mit tempfiles.

Die Frage war, tempfiles nicht zu verwenden. Dies kann jedoch auf die unerwünschte Verschmutzung von /tmp/ Mit tempfile zurückzuführen sein, falls die Shell stirbt. Bei kill -9 Werden einige trap 'rm "$tmpfile1" "$tmpfile2"' 0 Nicht ausgelöst.

Wenn Sie sich in einer Situation befinden, in der Sie tempfile verwenden können, aber niemals Trümmer zurücklassen wollen, ist hier ein Rezept.

Wieder heißt es catch() (wie meine andere Antwort ) und hat die gleiche Aufrufsyntax:

catch stdout stderr command args..

# Wrappers to avoid polluting the current Shell's environment with variables

: catch_read returncode FD variable
catch_read()
{
eval "$3=\"\`cat <&$2\`\"";
# You can use read instead to skip some fork()s.
# However read stops at the first NUL byte,
# also does no \n removal and needs bash 3 or above:
#IFS='' read -ru$2 -d '' "$3";
return $1;
}
: catch_1 tempfile variable comand args..
catch_1()
{
{
rm -f "$1";
"${@:3}" 66<&-;
catch_read $? 66 "$2";
} 2>&1 >"$1" 66<"$1";
}

: catch stdout stderr command args..
catch()
{
catch_1 "`tempfile`" "${2:-stderr}" catch_1 "`tempfile`" "${1:-stdout}" "${@:3}";
}

Was es macht:

  • Es werden zwei tempfiles für stdout und stderr erstellt. Diese werden jedoch fast sofort entfernt, so dass sie nur für eine sehr kurze Zeit verfügbar sind.

  • catch_1() fängt stdout (FD 1) in eine Variable und verschiebt stderr nach stdout, so dass der nächste ("left") catch_1 kann das fangen.

  • Die Verarbeitung in catch erfolgt von rechts nach links, sodass das linke catch_1 Zuletzt ausgeführt wird und stderr abfängt.

Das Schlimmste, was passieren kann, ist, dass einige temporäre Dateien auf /tmp/ Angezeigt werden, aber sie sind in diesem Fall immer leer. (Sie werden entfernt, bevor sie gefüllt werden.) Normalerweise sollte dies kein Problem sein, da tmpfs unter Linux ungefähr 128K-Dateien pro GB Hauptspeicher unterstützt.

  • Der angegebene Befehl kann auch auf alle lokalen Shell-Variablen zugreifen und diese ändern. Sie können also eine Shell-Funktion aufrufen, die Nebenwirkungen hat!

  • Dadurch wird der tempfile -Aufruf nur zweimal abgefragt.

Bugs:

  • Fehlende gute Fehlerbehandlung für den Fall, dass tempfile fehlschlägt.

  • Dies entfernt die Shell wie gewohnt \n. Siehe Kommentar in catch_read().

  • Sie können den Dateideskriptor 66 Nicht verwenden, um Daten an Ihren Befehl weiterzuleiten. Wenn Sie das brauchen, verwenden Sie einen anderen Deskriptor für die Umleitung, wie 42 (Beachten Sie, dass sehr alte Shells nur FDs bis zu 9 anbieten).

  • Dies kann nicht mit NUL-Bytes ($'\0') In stdout und stderr umgehen. (NUL wird einfach ignoriert. Bei der Variante read wird alles hinter einer NUL ignoriert.)

Zu Ihrer Information:

  • Unter Unix können wir auf gelöschte Dateien zugreifen, sofern Sie darauf verweisen (z. B. ein geöffnetes Dateihandle). Auf diese Weise können wir sie öffnen und dann entfernen.
2
Tino

Eine Abhilfemaßnahme, die schwierig, aber möglicherweise intuitiver ist als einige der Vorschläge auf dieser Seite, besteht darin, die Ausgabestreams zu markieren, zusammenzuführen und anschließend anhand der Tags zu teilen. Zum Beispiel könnten wir stdout mit einem "STDOUT" -Präfix versehen:

function someCmd {
    echo "I am stdout"
    echo "I am stderr" 1>&2
}

ALL=$({ someCmd | sed -e 's/^/STDOUT/g'; } 2>&1)
OUT=$(echo "$ALL" | grep    "^STDOUT" | sed -e 's/^STDOUT//g')
ERR=$(echo "$ALL" | grep -v "^STDOUT")

`` `

Wenn Sie wissen, dass stdout und/oder stderr eine eingeschränkte Form haben, können Sie ein Tag erstellen, das nicht mit dem zulässigen Inhalt in Konflikt steht.

0
Warbo

Hier ist eine einfachere Variante, die nicht ganz dem entspricht, was das OP wollte, die sich jedoch von allen anderen Optionen unterscheidet. Sie können alles bekommen, was Sie wollen, indem Sie die Dateideskriptoren neu anordnen.

Testbefehl:

%> cat xx.sh  
#!/bin/bash
echo stdout
>&2 echo stderr

was alleine macht:

%> ./xx.sh
stdout
stderr

Drucken Sie nun stdout, erfassen Sie stderr in einer Variablen und protokollieren Sie stdout in einer Datei

%> export err=$(./xx.sh 3>&1 1>&2 2>&3 >"out")
stdout
%> cat out    
stdout
%> echo
$err 
stderr

Oder loggen Sie stdout & capture stderr in eine Variable:

export err=$(./xx.sh 3>&1 1>out 2>&3 )
%> cat out
stdout
%> echo $err
stderr

Du hast die Idee.

0
Bruce Edge

ACHTUNG: (noch?) NICHT ARBEITEN!

Das Folgende scheint ein möglicher Weg zu sein, um es zum Laufen zu bringen, ohne temporäre Dateien zu erstellen, und auch nur unter POSIX sh; es benötigt jedoch base64 und aufgrund der codierung/decodierung kann es sein, dass es nicht so effizient ist und auch "größeren" speicher verwendet.

  • Selbst im einfachen Fall würde es schon scheitern, wenn die letzte stderr-Zeile kein Zeilenumbruch enthält. Dies kann zumindest in einigen Fällen behoben werden, indem exe durch "{exe; echo> & 2;}" ersetzt wird, d. H. Eine neue Zeile hinzugefügt wird.
  • Das Hauptproblem ist jedoch, dass alles rassig erscheint. Versuchen Sie es mit einer exe wie:

    exe () {cat /usr/share/hunspell/de_DE.dic cat /usr/share/hunspell/en_GB.dic> & 2}

und Sie werden sehen, dass z.B. Teile der base64-codierten Zeile befinden sich oben in der Datei, Teile am Ende und die nicht decodierten stderr-Daten in der Mitte.

Nun, auch wenn die Idee unten nicht zum Funktionieren gebracht werden kann (was ich annehme), kann sie als Anti-Beispiel für Leute dienen, die fälschlicherweise glauben, dass es dazu gebracht werden könnte, so zu arbeiten.

Idee (oder Anti-Beispiel):

#!/bin/sh

exe()
{
        echo out1
        echo err1 >&2
        echo out2
        echo out3
        echo err2 >&2
        echo out4
        echo err3 >&2
        echo -n err4 >&2
}


r="$(  { exe  |  base64 -w 0 ; }  2>&1 )"

echo RAW
printf '%s' "$r"
echo RAW

o="$( printf '%s' "$r" | tail -n 1 | base64 -d )"
e="$( printf '%s' "$r" | head -n -1  )"
unset r    

echo
echo OUT
printf '%s' "$o"
echo OUT
echo
echo ERR
printf '%s' "$e"
echo ERR

gibt (mit dem stderr-newline fix):

$ ./ggg 
RAW
err1
err2
err3
err4

b3V0MQpvdXQyCm91dDMKb3V0NAo=RAW

OUT
out1
out2
out3
out4OUT

ERR
err1
err2
err3
err4ERR

(Zumindest auf Debians Gedankenstrich und Bash)

0
calestyo

Wenn der Befehl 1) keine statistischen Nebenwirkungen und 2) rechenintensiv ist, ist die einfachste Lösung, ihn nur zweimal auszuführen. Ich habe dies hauptsächlich für Code verwendet, der während der Startsequenz ausgeführt wird, wenn Sie noch nicht wissen, ob die Festplatte funktionieren wird. In meinem Fall war es ein winziger some_command Es gab also keine Leistungseinbußen bei zweimaliger Ausführung, und der Befehl hatte keine Nebenwirkungen.

Der Hauptvorteil ist, dass dies sauber und leicht zu lesen ist. Die Lösungen hier sind ziemlich clever, aber ich würde es hassen, die zu sein, die ein Skript mit den komplizierteren Lösungen pflegen muss. Ich würde den einfachen Run-it-two-Ansatz empfehlen, wenn Ihr Szenario damit funktioniert, da er viel sauberer und einfacher zu warten ist.

Beispiel:

output=$(getopt -o '' -l test: -- "[email protected]")
errout=$(getopt -o '' -l test: -- "[email protected]" 2>&1 >/dev/null)
if [[ -n "$errout" ]]; then
        echo "Option Error: $errout"
fi

Auch dies ist nur in Ordnung, da getopt keine Nebenwirkungen hat. Ich weiß, dass es leistungssicher ist, weil mein übergeordneter Code dies weniger als 100 Mal während des gesamten Programms aufruft und der Benutzer niemals 100 getopt-Aufrufe gegenüber 200 getopt-Aufrufen bemerkt.

0
Hamy