Erfassen der Ausgabe mehrerer Zeilen in einer Bash-Variablen

583

Ich habe ein Skript 'myscript', das Folgendes ausgibt:

abc
def
ghi

In einem anderen Skript rufe ich auf:

declare RESULT=$(./myscript)

und $RESULTbekommt den Wert

abc def ghi

Gibt es eine Möglichkeit, das Ergebnis entweder mit den Zeilenumbrüchen oder mit dem Zeichen '\ n' zu speichern, damit ich es mit ' echo -e' ausgeben kann ?

Parker
quelle
1
es überrascht mich. hast du nicht $ (cat ./myscipt)? sonst hätte ich erwartet, dass es versucht, die Befehle abc, def und ghi auszuführen
Johannes Schaub - litb
@litb: ja, ich denke schon; Sie können auch $ (<./ myscript) verwenden, wodurch die Ausführung eines Befehls vermieden wird.
Jonathan Leffler
2
(NB: Die beiden obigen Kommentare beziehen sich auf eine Überarbeitung der gestarteten Frage. Ich habe ein Skript 'Myscript', das Folgendes enthält , was zu den Fragen führte. Die aktuelle Überarbeitung der Frage ( Ich habe ein Skript '. Myscript ', das Folgendes ausgibt ) macht die Kommentare überflüssig. Die Überarbeitung erfolgt jedoch vom 11.11.2011, lange nachdem die beiden Kommentare abgegeben wurden.
Jonathan Leffler

Antworten:

1082

Tatsächlich enthält RESULT, was Sie wollen - um zu demonstrieren:

echo "$RESULT"

Was Sie zeigen, ist das, was Sie bekommen von:

echo $RESULT

Wie in den Kommentaren erwähnt, besteht der Unterschied darin, dass (1) die doppelt zitierte Version der Variablen ( echo "$RESULT") den internen Abstand des Werts genau so beibehält, wie er in der Variablen dargestellt wird - Zeilenumbrüche, Tabulatoren, mehrere Leerzeichen und alles - während (2) ) Die nicht zitierte Version ( echo $RESULT) ersetzt jede Folge von einem oder mehreren Leerzeichen, Tabulatoren und Zeilenumbrüchen durch ein einzelnes Leerzeichen. Somit behält (1) die Form der Eingabevariablen bei, während (2) eine möglicherweise sehr lange einzelne Ausgabezeile mit durch einzelne Leerzeichen getrennten "Wörtern" erzeugt (wobei ein "Wort" eine Folge von Nicht-Leerzeichen ist; dies ist nicht erforderlich in keinem der Wörter alphanumerisch sein).

Jonathan Leffler
quelle
69
@troelskn: Der Unterschied besteht darin, dass (1) die doppelt zitierte Version der Variablen den internen Abstand des Werts genau so beibehält, wie er in der Variablen, in Zeilenumbrüchen, Tabulatoren, mehreren Leerzeichen und allem dargestellt ist, während (2) die nicht zitierte Version ersetzt Jede Folge von einem oder mehreren Leerzeichen, Tabulatoren und Zeilenumbrüchen mit einem einzigen Leerzeichen. Somit behält (1) die Form der Eingabevariablen bei, während (2) eine möglicherweise sehr lange einzelne Ausgabezeile mit durch einzelne Leerzeichen getrennten "Wörtern" erzeugt (wobei ein "Wort" eine Folge von Nicht-Leerzeichen ist; dies ist nicht erforderlich in keinem der Wörter alphanumerisch sein).
Jonathan Leffler
23
Um die Antwort verständlicher zu machen: Die Antwort besagt, dass das Echo "$ RESULT" die Zeilenumbruchung beibehält, während das Echo $ RESULT dies nicht tut.
Yu Shen
In einigen Situationen bleiben Zeilenumbrüche und führende Leerzeichen nicht erhalten.
CommaToast
3
@CommaToast: Wirst du das näher erläutern? Nachgestellte Zeilenumbrüche gehen verloren. Daran führt kein einfacher Weg vorbei. Führende Leerzeichen - Mir sind keine Umstände bekannt, unter denen sie verloren gehen.
Jonathan Leffler
90

Ein weiterer Fallstrick dabei ist , dass Kommandosubstitution - $()- Streifen Hinterzeilenumbrüche. Wahrscheinlich nicht immer wichtig, aber wenn Sie wirklich genau das beibehalten möchten, was ausgegeben wurde, müssen Sie eine andere Zeile und einige Anführungszeichen verwenden:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

Dies ist besonders wichtig, wenn Sie alle möglichen Dateinamen verarbeiten möchten (um undefiniertes Verhalten wie das Bearbeiten der falschen Datei zu vermeiden).

l0b0
quelle
4
Ich musste eine Weile mit einer kaputten Shell arbeiten, die den letzten Zeilenumbruch nicht aus der Befehlsersetzung entfernte (es ist keine Prozessersetzung ), und sie hat fast alles kaputt gemacht. Wenn Sie dies getan haben pwd=`pwd`; ls $pwd/$file, haben Sie vor dem eine neue Zeile erhalten /, und es hat nicht geholfen, den Namen in doppelte Anführungszeichen zu setzen. Es wurde schnell behoben. Dies war in den Jahren 1983-5 auf ICL Perq PNX; Die Shell hatte keine $PWDeingebaute Variable.
Jonathan Leffler
19

Wenn Sie an bestimmten Zeilen interessiert sind, verwenden Sie ein Ergebnis-Array:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"
user2574210
quelle
3
Wenn die Zeilen Leerzeichen enthalten, werden Felder (Inhalte zwischen Leerzeichen) anstelle von Zeilen gezählt.
Liam
2
Sie würden die readarraySubstitution anstelle der Befehlssubstitution verwenden und verarbeiten : readarray -t RESULT < <(./myscript>.
Chepner
15

Zusätzlich zu der Antwort von @ l0b0 Ich hatte gerade die Situation , wo ich beide benötigen durch das Skript all nachgestellten newlines Ausgabe halten und überprüft das Rückkehrcode des Skripts. Und das Problem mit der Antwort von l0b0 ist, dass das 'echo x' $ zurückgesetzt hat? zurück auf Null ... also habe ich es geschafft, diese sehr listige Lösung zu finden:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"
Lurchman
quelle
Das ist sehr schön, ich hatte tatsächlich den Anwendungsfall, in dem ich eine die ()Funktion haben möchte , die einen beliebigen Exit-Code und optional eine Nachricht akzeptiert, und ich wollte eine andere usage ()Funktion verwenden, um die Nachricht zu liefern, aber die Zeilenumbrüche wurden immer wieder gequetscht, hoffentlich kann ich das das umgehen.
Dragon788
1

Nachdem ich die meisten Lösungen hier ausprobiert hatte, war das Einfachste, was ich fand, das Offensichtliche - die Verwendung einer temporären Datei. Ich bin mir nicht sicher, was Sie mit Ihrer mehrzeiligen Ausgabe machen möchten, aber Sie können dann zeilenweise mit read damit umgehen. Das Einzige, was Sie nicht wirklich tun können, ist, alles einfach in dieselbe Variable zu stecken, aber für die meisten praktischen Zwecke ist dies viel einfacher zu handhaben.

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

Schneller Hack, damit es die angeforderte Aktion ausführt:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

Beachten Sie, dass dies eine zusätzliche Zeile hinzufügt. Wenn du daran arbeitest, kannst du es umschreiben, ich bin einfach zu faul.


BEARBEITEN: Während dieser Fall perfekt funktioniert, sollten sich die Leser bewusst sein, dass Sie Ihr stdin leicht in der while-Schleife quetschen können, wodurch Sie ein Skript erhalten, das eine Zeile ausführt, stdin löscht und beendet. Wie wird ssh das tun, denke ich? Ich habe es erst kürzlich gesehen, andere Codebeispiele hier: /unix/24260/reading-lines-from-a-file-with-bash-for-vs-while

Ein Mal noch! Diesmal mit einem anderen Dateihandle (stdin, stdout, stderr sind 0-2, also können wir & 3 oder höher in bash verwenden).

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

Sie können auch mktemp verwenden, dies ist jedoch nur ein kurzes Codebeispiel. Die Verwendung für mktemp sieht folgendermaßen aus:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

Verwenden Sie dann $ filenamevar wie den tatsächlichen Namen einer Datei. Wahrscheinlich muss hier nicht erklärt werden, aber jemand hat sich in den Kommentaren beschwert.

user1279741
quelle
Ich habe auch andere Lösungen ausprobiert, mit Ihrem ersten Vorschlag habe ich endlich mein Skript zum
Laufen gebracht
1
Downvote: Dies ist übermäßig komplex und vermeidet nicht mehrere häufige bashFallstricke .
Tripleee
Ya jemand hat mir neulich von dem seltsamen Problem mit dem Stdin-Filehandle erzählt und ich war wie "wow". Lass mich schnell etwas hinzufügen.
user1279741
In Bash können Sie die readBefehlserweiterung verwenden -u 3, um aus Dateideskriptor 3 zu lesen.
Jonathan Leffler
Warum nicht, während ... tun ... getan << (. Myscript.sh)
jtgd
0

Wie wäre es damit, es liest jede Zeile in eine Variable und das kann anschließend verwendet werden! Angenommen, die MyScript-Ausgabe wird in eine Datei namens MyScript_output umgeleitet

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'
Rahul Reddy
quelle
4
Nun, das ist keine Bash, das ist awk.
Vadipp