Speichern Sie die Ausgabe des Befehls, der die Umgebung ändert, in eine Variable

8

Wie speichere ich die Ausgabe eines Befehls, der die Umgebung in eine Variable ändert?

Ich benutze 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 von fin Variable speichern c, also habe ich 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

Ich habe hier also keine Umgebungsänderung.

Wie speichere ich die Ausgabe des Befehls (nicht nur der Funktion) in einer Variablen und speichere Umgebungsänderungen?

Ich weiß, dass dies eine $(...)neue Subshell öffnet und das ist das Problem, aber ist es möglich, eine Problemumgehung durchzuführen?

Faramir
quelle
Richtig, so dass die Frage ist , dass $aund $bsind lokale Variablen in Ihrer fFunktion. Sie könnten exportsie, aber das scheint lückenhaft.
HalosGhost
1
@ HalosGhost: Nein, das glaube ich nicht. Schauen Sie sich das erste Beispiel an: Dies …; f; echo $a; …führt zu 3einem Echo, ebenso fwie das Ändern einer Shell-Variablen (und nicht nur ihrer eigenen lokalen Variablen).
Scott

Antworten:

2

Wenn Sie Bash 4 oder höher verwenden, können Sie Coprozesse verwenden :

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 ausgegeben

a 3
b 4
c Sun Jun 29 10:08:15 NZST 2014: 3 4

coprocErstellt einen neuen Prozess, in dem ein bestimmter Befehl ausgeführt wird (hier cat). Es speichert die PID in COPROC_PIDund Standard-Deskriptoren für Ausgabe- / Eingabedateien in einem Array COPROC(genau wie pipe(2)oder siehe hier oder hier ).

Hier führen wir die Funktion mit Standardausgabe aus, die auf unseren laufenden Coprozess zeigt cat, und dann readvon dort aus. Da catnur die Eingabe zurückgespuckt wird, erhalten wir die Ausgabe der Funktion in unsere Variable. exec {COPROC[1]}>&-Schließt einfach den Dateideskriptor, catdamit Sie nicht ewig warten müssen.


Beachten Sie, dass jeweils readnur eine Zeile benötigt wird. Sie können mapfileein Array von Zeilen abrufen oder einfach den Dateideskriptor verwenden, wie Sie ihn auch verwenden möchten.

exec {COPROC[1]}>&-funktioniert in aktuellen Versionen von Bash, aber in früheren Versionen der 4er-Serie müssen Sie den Dateideskriptor zuerst in einer einfachen Variablen speichern : fd=${COPROC[1]}; exec {fd}>&-. Wenn Ihre Variable nicht gesetzt ist, wird die Standardausgabe geschlossen.


Wenn Sie eine 3er-Version von Bash verwenden, können Sie den gleichen Effekt erzielen mkfifo, aber es ist nicht viel besser, als zu diesem Zeitpunkt eine tatsächliche Datei zu verwenden.

Michael Homer
quelle
Ich kann nur Bash 3.1 verwenden. Die Rohre sind schwer zu bedienen. Ist mktemp und ist die einzige Option? Gibt es hier keine spezielle Syntax oder Option zum Ausführen eines Befehls und zum Abrufen der Ausgabe, sondern mit Änderungen der Umgebung?
Faramir
Sie können mkfifo verwenden, um eine Named Pipe als Ersatz für Coproc zu erstellen, ohne die Daten tatsächlich auf die Festplatte zu schreiben. Damit würden Sie die Ausgabe auf das FIFO umleiten und von diesem anstelle des Coprozesses eingeben. Sonst nein.
Michael Homer
+1 für die Anpassung meiner Antwort, um keine Datei zu verwenden. In meinen Tests beendet der exec {COPROC[1]}>&-Befehl (zumindest manchmal) den Coprozess sofort , sodass seine Ausgabe nicht mehr zum Lesen verfügbar ist. coproc { cat; sleep 1; }scheint besser zu funktionieren. (Möglicherweise müssen Sie die erhöhen, 1wenn Sie mehr als eine Zeile lesen.)
Scott
1
Es gibt eine konventionelle, gut verstandene Bedeutung von "tatsächlicher Datei", die hier verwendet wird und die Sie absichtlich ignorieren.
Michael Homer
1
"Tatsächliche Datei" bezeichnet eine Datei auf der Festplatte , wie sie von allen hier verstanden wird, außer angeblich von Ihnen. Niemand hat jemals einen vernünftigen Leser impliziert oder zugelassen, daraus zu schließen, dass eines von beiden mit Argumenten oder Umgebungsvariablen identisch ist. Ich bin nicht daran interessiert, diese Diskussion fortzusetzen.
Michael Homer
2

Wenn Sie bereits auf die fAusführung in derselben Shell wie der Aufrufer zählen und somit Variablen wie aund ändern können b, können Sie die Funktion auch einfach festlegen c. Mit anderen Worten:

$ 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 kann sein, dass die Ausgabevariable unterschiedliche Namen (c1, c2 usw.) haben muss, wenn sie an verschiedenen Stellen aufgerufen wird. Sie können dies jedoch beheben, indem Sie die Funktion c_TEMP festlegen und die Aufrufer c1=$c_TEMPusw.

godlygeek
quelle
1

Es ist ein Kluge, aber versuchen Sie es

f > c.file
c=$(cat c.file)

(und optional rm c.file).

Scott
quelle
Vielen Dank. Das ist eine einfache Antwort und funktioniert, hat aber einige Nachteile. Ich weiß, dass ich verwenden kann mktemp, aber ich möchte keine zusätzlichen Dateien erstellen.
Faramir
1

Weisen Sie einfach alle Variablen zu und schreiben Sie die Ausgabe gleichzeitig.

f() { c= ; echo "${c:=$(date): $((a=3)) $((b=4))}" ; }

Wenn Sie dies 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 tragbarer POSIX-Code ist. Ich habe anfangs c=die ''Nullzeichenfolge festgelegt, weil die Parametererweiterung die gleichzeitige Variablenzuweisung + Auswertung entweder nur auf numerische (wie $((var=num))) oder von null oder nicht vorhandenen Werten beschränkt - oder mit anderen Worten, Sie können eine Variable nicht gleichzeitig auf eine beliebige Zeichenfolge setzen und auswerten wenn dieser Variablen bereits ein Wert zugewiesen ist. Also stelle ich einfach sicher, dass es leer ist, bevor ich es versuche. Wenn ich cvor dem Versuch, es zuzuweisen, nicht leer wäre, würde die Erweiterung nur den alten Wert zurückgeben.

Nur um zu demonstrieren:

sh -c '
    c=oldval a=1
    echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2

newvalwird nicht$c inline zugewiesen, da oldvalin erweitert wird ${word}, während die inline- $((arithmetische =Zuweisung ))immer erfolgt. Aber wenn $c nein hat oldval und leer oder nicht gesetzt ist ...

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

... wird dann newvalsofort zugeordnet und erweitert $c.

Alle anderen Möglichkeiten, dies zu tun, beinhalten irgendeine Form der sekundären Bewertung. Nehmen wir zum Beispiel an, ich wollte die Ausgabe f()einer Variablen zuweisen, die namean einem Punkt und varan einem anderen benannt ist. Wie derzeit geschrieben, funktioniert dies nicht, ohne die Variable im Bereich des Aufrufers festzulegen. Ein anderer Weg 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 wie oben genannt 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 $ENVoder 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

Bei der zweimaligen Auswertung ist es wahrscheinlich am schwierigsten, 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 exportim Gegensatz zu evalist weitaus sicherer.

Im obigen Beispiel wird f()zuerst $foutdie ''Nullzeichenfolge zugewiesen und dann Positionsparameter festgelegt, um auf gültige Variablennamen zu testen. Wenn beide Tests nicht bestanden werden, wird eine Nachricht ausgegeben stderrund der Standardwert von foutzugewiesen $fout_name. Unabhängig von den Tests wird jedoch $fout_nameimmer entweder foutoder dem von Ihnen $foutangegebenen Namen zugewiesen , und optional wird Ihrem angegebenen Namen immer der Ausgabewert der Funktion zugewiesen. Um dies zu demonstrieren, habe ich diese kleine forSchleife geschrieben:

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 einige mit Variablennamen und Parametererweiterungen herum. Wenn Sie eine Frage haben, fragen Sie einfach. Das läuft nur die wenigen Zeilen in der hier bereits dargestellten Funktion. Zumindest ist zu erwähnen, dass sich die Variablen $aund $bunterschiedlich verhalten, je nachdem, ob sie beim Aufruf definiert oder bereits festgelegt wurden. Trotzdem formacht der fast nichts anderes, als den Datensatz zu formatieren und bereitzustellen von f(). Guck mal:

###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'
mikeserv
quelle