
Wie speichere ich die Ausgabe eines Befehls, der die Umgebung ändert, in einer Variablen?
Ich verwende die Bash-Shell.
Angenommen, ich habe:
function f () { a=3; b=4 ; echo "`date`: $a $b"; }
Und jetzt kann ich Befehle verwenden, um Folgendes auszuführen f
:
$ a=0; b=0; f; echo $a; echo $b; echo $c
Sat Jun 28 21:27:08 CEST 2014: 3 4
3
4
aber ich möchte die Ausgabe in f
einer Variable speichern c
, also habe ich Folgendes versucht:
a=0; b=0; c=""; c=$(f); echo $a; echo $b; echo $c
aber leider habe ich:
0
0
Sat Jun 28 21:28:03 CEST 2014: 3 4
also habe ich hier keine Umgebungsänderung.
Wie speichere ich die Ausgabe eines Befehls (nicht nur einer Funktion) in einer Variablen und speichere Umgebungsänderungen?
Ich weiß, dass dadurch $(...)
eine neue Subshell geöffnet wird und dass dort das Problem liegt, aber gibt es eine Möglichkeit, das Problem zu umgehen?
Antwort1
Wenn Sie Bash 4 oder höher verwenden, können SieKoprozesse:
function f () { a=3; b=4 ; echo "`date`: $a $b"; }
coproc cat
f >&${COPROC[1]}
exec {COPROC[1]}>&-
read c <&${COPROC[0]}
echo a $a
echo b $b
echo c $c
wird ausgeben
a 3
b 4
c Sun Jun 29 10:08:15 NZST 2014: 3 4
coproc
erstellt einen neuen Prozess, der einen bestimmten Befehl ausführt (hier: cat
). Er speichert die PID in COPROC_PID
und die Standard-Ausgabe-/Eingabedateideskriptoren in einem Array COPROC
(genau wiepipe(2)
oder sieheHieroderHier).
Hier führen wir die Funktion mit der Standardausgabe aus, die auf unseren laufenden Coprozess zeigt cat
, und dannread
davon. Da cat
just seine Eingabe wieder ausspuckt, erhalten wir die Ausgabe der Funktion in unserer Variable. exec {COPROC[1]}>&-
just schließt den Dateideskriptor, damit cat
wir nicht ewig warten müssen.
Beachten Sie, dass read
immer nur eine Zeile gleichzeitig benötigt wird. Sie können verwenden, mapfile
um ein Array von Zeilen abzurufen, oder einfach den Dateideskriptor verwenden, wie auch immer Sie ihn auf andere Weise verwenden möchten.
exec {COPROC[1]}>&-
funktioniert in aktuellen Versionen von Bash, aber in früheren Versionen der 4er-Reihe müssen Sie den Dateideskriptor zuerst in einer einfachen Variable speichern: fd=${COPROC[1]}; exec {fd}>&-
. Wenn Ihre Variable nicht festgelegt ist, wird die Standardausgabe geschlossen.
Wenn Sie eine 3er-Version von Bash verwenden, können Sie den gleichen Effekt erzielen mitmkfifo
, aber es ist nicht viel besser, als an diesem Punkt eine echte Datei zu verwenden.
Antwort2
Wenn Sie bereits davon ausgehen, dass der Hauptteil von in derselben Shell wie der Aufrufer ausgeführt wird und Sie somit Variablen wie und f
ändern können , warum machen Sie dann nicht auch die Funktion einfach festgelegt ? Mit anderen Worten:a
b
c
$ function f () { a=3; b=4 ; c=$(echo "$(date): $a $b"); }
$ a=0; b=0; f; echo $a; echo $b; echo $c
3
4
Mon Jun 30 14:32:00 EDT 2014: 3 4
Ein möglicher Grund könnte sein, dass die Ausgabevariable unterschiedliche Namen (c1, c2 usw.) haben muss, wenn sie an unterschiedlichen Stellen aufgerufen wird. Dies könnte jedoch dadurch behoben werden, dass die Funktion c_TEMP einstellt und die Aufrufer dies tun c1=$c_TEMP
usw.
Antwort3
Es ist ein kluge, aber versuchen Sie
f > c.file
c=$(cat c.file)
(und optional rm c.file
).
Antwort4
Weisen Sie einfach alle Variablen zu und schreiben Sie die Ausgabe gleichzeitig.
f() { c= ; echo "${c:=$(date): $((a=3)) $((b=4))}" ; }
Wenn Sie dies nun tun:
f ; echo "$a $b $c"
Ihre Ausgabe ist:
Tue Jul 1 04:58:17 PDT 2014: 3 4
3 4 Tue Jul 1 04:58:17 PDT 2014: 3 4
Beachten Sie, dass dies vollständig POSIX-portabler Code ist. Ich habe zunächst c=
auf die ''
Nullzeichenfolge gesetzt, da die Parametererweiterung die gleichzeitige Variablenzuweisung + -auswertung auf entweder nur numerische(wie $((var=num))
)oder von Null- oder nicht vorhandenen Werten - oder, mit anderen Worten, Sie können nicht gleichzeitig festlegenUndwertet eine Variable in eine beliebige Zeichenfolge aus, wenn dieser Variable bereits ein Wert zugewiesen ist. Ich stelle also einfach sicher, dass sie leer ist, bevor ich es versuche. Wenn ich sie c
vor dem Zuweisungsversuch nicht geleert hätte, würde die Erweiterung nur den alten Wert zurückgeben.
Nur zur Demonstration:
sh -c '
c=oldval a=1
echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2
newval
Istnichtzugewiesen an $c
inline, weil oldval
in erweitert wird ${word}
, während die $((
arithmetische Inline- =
Zuweisung ))
immer erfolgt. Aber wenn$c
hat kein oldval
und ist leer oder nicht festgelegt …
sh -c '
c=oldval a=1
echo ${c:=newval} $((a=a+a))
c= a=$((a+a))
echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2
newval 8
... newval
wird dann sofort zugewiesen und in erweitert $c
.
Alle anderen Möglichkeiten, dies zu tun, beinhalten eine Art sekundäre Auswertung. Nehmen wir beispielsweise an, ich möchte die Ausgabe f()
einer Variable zuweisen, die name
an einer Stelle und var
an einer anderen benannt ist. In der aktuellen Schreibweise funktioniert dies nicht, ohne die Variable im Gültigkeitsbereich des Anrufers festzulegen. Eine andere Möglichkeit könnte jedoch so aussehen:
f(){ fout_name= fout= set -- "${1##[0-9]*}" "${1%%*[![:alnum:]]*}"
(: ${2:?invalid or unspecified param - name set to fout}) || set --
export "${fout_name:=${1:-fout}}=${fout:=$(date): $((a=${a:-50}+1)) $((b=${b:-100}-4))}"
printf %s\\n "$fout"
}
f &&
printf %s\\n \
"$fout_name" \
"$fout" \
"$a" "$b"
Ich habe unten ein besser formatiertes Beispiel bereitgestellt, aber mit dem obigen Aufruf lautet die Ausgabe:
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul 2 02:27:07 PDT 2014: 51 96
fout
Wed Jul 2 02:27:07 PDT 2014: 51 96
51
96
Oder mit anderen $ENV
oder Argumenten:
b=9 f myvar &&
printf %s\\n \
"$fout_name" \
"$fout" \
"$myvar" \
"$a" "$b"
###OUTPUT###
Tue Jul 1 19:56:42 PDT 2014: 52 5
myvar
Tue Jul 1 19:56:42 PDT 2014: 52 5
Tue Jul 1 19:56:42 PDT 2014: 52 5
52
5
Das wahrscheinlich Schwierigste bei der doppelten Auswertung ist, sicherzustellen, dass Variablen keine Anführungszeichen brechen und zufälligen Code ausführen. Je öfter eine Variable ausgewertet wird, desto schwieriger wird es. Die Parametererweiterung hilft hier sehr, und die Verwendung von „as“ export
anstelle von „as“ eval
ist weitaus sicherer.
Im obigen Beispiel f()
wird zuerst $fout
die ''
Nullzeichenfolge zugewiesen und dann werden Positionsparameter gesetzt, um nach gültigen Variablennamen zu suchen. Wenn beide Tests nicht erfolgreich sind, wird eine Meldung an ausgegeben stderr
und der Standardwert von fout
wird zugewiesen $fout_name
. Unabhängig von den Tests wird jedoch immer entweder oder der von Ihnen angegebene Name $fout_name
zugewiesen und und optional Ihr angegebener Name werden immer dem Ausgabewert der Funktion zugewiesen. Um dies zu demonstrieren, habe ich diese kleine Schleife geschrieben:fout
$fout
for
for v in var '' "wr;\' ong"
do sleep 10 &&
a=${a:+$((a*2))} f "$v" || break
echo "${v:-'' #null}"
printf '#\t"$%s" = '"'%s'\n" \
a "$a" b "$b" \
fout_name "$fout_name" \
fout "$fout" \
'(eval '\''echo "$'\''"$fout_name"\")' \
"$(eval 'echo "$'"$fout_name"\")"
done
Es spielt ein bisschen mit Variablennamen und Parametererweiterungen herum. Wenn du eine Frage hast, frag einfach. Dasnurführt dieselben paar Zeilen in der hier bereits dargestellten Funktion aus. Es ist jedoch zumindest erwähnenswert, dass sich die Variablen $a
und $b
unterschiedlich verhalten, je nachdem, ob sie beim Aufruf definiert oder bereits festgelegt sind. Dennoch for
tut die fast nichts, außer den Datensatz zu formatieren und von bereitzustellen f()
. Sehen Sie sich das an:
###OUTPUT###
Wed Jul 2 02:50:17 PDT 2014: 51 96
var
# "$a" = '51'
# "$b" = '96'
# "$fout_name" = 'var'
# "$fout" = 'Wed Jul 2 02:50:17 PDT 2014: 51 96'
# "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul 2 02:50:17 PDT 2014: 51 96'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul 2 02:50:27 PDT 2014: 103 92
'' #null
# "$a" = '103'
# "$b" = '92'
# "$fout_name" = 'fout'
# "$fout" = 'Wed Jul 2 02:50:27 PDT 2014: 103 92'
# "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul 2 02:50:27 PDT 2014: 103 92'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul 2 02:50:37 PDT 2014: 207 88
wr;\' ong
# "$a" = '207'
# "$b" = '88'
# "$fout_name" = 'fout'
# "$fout" = 'Wed Jul 2 02:50:37 PDT 2014: 207 88'
# "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul 2 02:50:37 PDT 2014: 207 88'