Warum kann ich keine Umgebungsvariable angeben und in derselben Befehlszeile wiedergeben?

89

Betrachten Sie diesen Ausschnitt:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

Hier habe ich $SOMEVARin AAAder ersten Zeile eingestellt - und wenn ich es in der zweiten Zeile wiedergebe, erhalte ich den AAAInhalt wie erwartet.

Aber dann, wenn ich versuche, die Variable in derselben Befehlszeile wie die echofolgende anzugeben :

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

... Ich bekomme nicht BBBwie erwartet - ich bekomme den alten Wert ( AAA).

Sollen die Dinge so sein? Wenn ja, wie kommt es, dass Sie Variablen wie angeben LD_PRELOAD=/... program args ...und es funktionieren lassen können? Was vermisse ich?

sdaau
quelle
2
Dies funktioniert, wenn Sie die Zuweisung als separate Anweisung festlegen oder wenn Sie ein Skript mit einer eigenen Umgebung aufrufen, jedoch nicht, wenn Sie einem Befehl in der aktuellen Umgebung vorangestellt haben. Interessant!
Todd A. Jacobs
1
Die Grund , warum LD_PRELOADArbeiten sind , dass der Variable in dem Programm gesetzt Umgebung - nicht auf seiner Kommandozeile.
Bis auf weiteres angehalten.

Antworten:

100

Was Sie sehen, ist das erwartete Verhalten. Das Problem besteht darin, dass die übergeordnete Shell $SOMEVARin der Befehlszeile ausgewertet wird , bevor der Befehl mit der geänderten Umgebung aufgerufen wird. Sie müssen die Auswertung $SOMEVARaufgeschoben erhalten, bis die Umgebung eingestellt ist.

Ihre unmittelbaren Optionen umfassen:

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz.
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'.

Beide verwenden einfache Anführungszeichen, um zu verhindern, dass die übergeordnete Shell ausgewertet wird $SOMEVAR. Es wird erst ausgewertet, nachdem es in der Umgebung festgelegt wurde (vorübergehend für die Dauer des einzelnen Befehls).

Eine andere Möglichkeit ist die Verwendung der Sub-Shell-Notation (wie auch von Marcus Kuhn in seiner Antwort vorgeschlagen ):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

Die Variable wird nur in der Unterschale festgelegt

Jonathan Leffler
quelle
Super, @JonathanLeffler - vielen Dank für die Erklärung; Prost!
Sdaau
Der Zusatz von @ markus-kuhn ist schwer zu überschätzen.
Alex Che
37

Das Problem, überarbeitet

Ehrlich gesagt ist das Handbuch in diesem Punkt verwirrend. Das GNU Bash Handbuch sagt:

Die Umgebung für einfache Befehle oder Funktionen [beachten Sie, dass dies integrierte Funktionen ausschließt] kann vorübergehend erweitert werden, indem Parameterzuweisungen vorangestellt werden, wie unter Shell-Parameter beschrieben. Diese Zuweisungsanweisungen wirken sich nur auf die Umgebung aus, die von diesem Befehl angezeigt wird.

Wenn Sie den Satz wirklich analysieren, heißt es, dass die Umgebung für den Befehl / die Funktion geändert wird, nicht jedoch die Umgebung für den übergeordneten Prozess. Das wird also funktionieren:

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

weil die Umgebung für den Befehl env vor seiner Ausführung geändert wurde. Dies wird jedoch nicht funktionieren:

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

aufgrund dessen, wann die Parametererweiterung von der Shell durchgeführt wird.

Dolmetscherschritte

Ein weiterer Teil des Problems besteht darin, dass Bash diese Schritte für seinen Interpreter definiert:

  1. Liest seine Eingabe aus einer Datei (siehe Shell-Skripte), aus einer als Argument angegebenen Zeichenfolge für die Aufrufoption -c (siehe Aufrufen von Bash) oder vom Terminal des Benutzers.
  2. Unterbricht die Eingabe in Wörter und Operatoren und befolgt dabei die in Quoting beschriebenen Anführungsregeln. Diese Token sind durch Metazeichen getrennt. Die Alias-Erweiterung wird durch diesen Schritt ausgeführt (siehe Aliase).
  3. Analysiert die Token in einfache und zusammengesetzte Befehle (siehe Shell-Befehle).
  4. Führt die verschiedenen Shell-Erweiterungen aus (siehe Shell-Erweiterungen) und unterteilt die erweiterten Token in Listen mit Dateinamen (siehe Dateinamenerweiterung) sowie Befehle und Argumente.
  5. Führt alle erforderlichen Umleitungen durch (siehe Umleitungen) und entfernt die Umleitungsoperatoren und ihre Operanden aus der Argumentliste.
  6. Führt den Befehl aus (siehe Ausführen von Befehlen).
  7. Wartet optional auf den Abschluss des Befehls und sammelt seinen Exit-Status (siehe Exit-Status).

Was hier passiert, ist, dass Buildins keine eigene Ausführungsumgebung erhalten, sodass sie die geänderte Umgebung nie sehen. Darüber hinaus einfache Befehle (zB / bin / echo) Sie erhalten eine modifizierte ennvironment (weshalb das env Beispiel arbeitete) , aber die Shell - Erweiterung ist in der stattfindet aktuellen Umgebung in Schritt # 4.

Mit anderen Worten, Sie übergeben 'aaa $ TESTVAR ccc' nicht an / bin / echo; Sie übergeben die interpolierte Zeichenfolge (wie in der aktuellen Umgebung erweitert) an / bin / echo. In diesem Fall übergeben Sie einfach 'aaa ccc' an den Befehl , da die aktuelle Umgebung kein TESTVAR enthält .

Zusammenfassung

Die Dokumentation könnte viel klarer sein. Gut, dass es einen Stapelüberlauf gibt!

Siehe auch

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment

Todd A. Jacobs
quelle
Ich hatte dies bereits positiv bewertet - aber ich bin gerade auf diese Frage zurückgekommen, und dieser Beitrag enthält genau die Hinweise, die ich brauche. Vielen Dank, @CodeGnome!
Sdaau
Ich weiß nicht , ob Bash hat in diesem Bereich verändert , seit dieser Antwort geschrieben wurde, aber das Präfix Variablenzuweisung tun Arbeit mit builtins jetzt. Zum Beispiel FOO=foo eval 'echo $FOO'druckt foowie erwartet. Dies bedeutet, dass Sie Dinge wie tun können IFS="..." read ....
Will Vousden
Ich denke, was passiert ist, dass Bash seine eigene Umgebung vorübergehend ändert und sie wiederherstellt, sobald der Befehl ausgeführt wurde, was seltsame Nebenwirkungen haben kann.
Will Vousden
22

Verwenden Sie, um das zu erreichen, was Sie möchten

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

Grund:

  • Sie müssen die Zuweisung durch Semikolon oder neue Zeile vom nächsten Befehl trennen, andernfalls wird sie nicht ausgeführt, bevor die Parametererweiterung für den nächsten Befehl (Echo) erfolgt.

  • Sie müssen die Zuweisung in einer Subshell- Umgebung vornehmen , um sicherzustellen, dass sie nicht über die aktuelle Zeile hinaus bestehen bleibt.

Diese Lösung ist kürzer, übersichtlicher und effizienter als einige der anderen vorgeschlagenen, insbesondere wird kein neuer Prozess erstellt.

Markus Kuhn
quelle
3
Für zukünftige Googler, die hier landen: Dies ist wahrscheinlich die beste Antwort auf diese Frage. Um es weiter zu verkomplizieren, müssen Sie die Zuweisung exportieren, wenn sie in der Umgebung des Befehls verfügbar sein soll. Die Unterschale verhindert weiterhin, dass die Zuweisung bestehen bleibt. (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')")
eaj
@eaj Um eine Shell-Variable in einen einzelnen externen Programmaufruf zu exportieren, wie in Ihrem Beispiel, verwenden Sie einfachSOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"
Markus Kuhn
10

Der Grund ist, dass dies eine Umgebungsvariable für eine Zeile festlegt. Aber echomacht nicht die Erweiterung, bashtut. Daher wird Ihre Variable tatsächlich erweitert, bevor der Befehl ausgeführt wird, obwohl sie SOME_VARsich BBBim Kontext des Echo-Befehls befindet.

Um den Effekt zu sehen, können Sie Folgendes tun:

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

Hier wird die Variable erst erweitert, wenn der untergeordnete Prozess ausgeführt wird, sodass Sie den aktualisierten Wert sehen. Wenn Sie SOME_VARIABLEdie übergeordnete Shell erneut überprüfen , ist dies AAAerwartungsgemäß weiterhin der Fall.

Fataler Fehler
quelle
1
+1 für die korrekte Erklärung, warum es nicht wie geschrieben funktioniert, und für eine praktikable Problemumgehung.
Jonathan Leffler
1
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

Benutze einen ; Anweisungen zu trennen, die sich in derselben Zeile befinden.

Kyros
quelle
1
Das funktioniert, ist aber nicht ganz der Punkt. Die Idee ist, die Umgebung nur für einen Befehl festzulegen, nicht dauerhaft wie Ihre Lösung.
Jonathan Leffler
Danke dafür @Kyros; Ich weiß nicht, warum ich das jetzt verpasst habe :) Ich wandere immer noch herum, wie LD_PRELOADund so kann man vor einer ausführbaren Datei ohne Semikolon arbeiten ... Nochmals vielen Dank - Prost!
Sdaau
@ JonathanLeffler - in der Tat war das die Idee; Ich wusste nicht, dass das Semikolon die Änderung dauerhaft macht - danke, dass Sie das bemerkt haben!
Sdaau
1

Hier ist eine Alternative:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz
brian
quelle
Unabhängig davon, ob Sie die Befehle verwenden &&oder ;trennen, bleibt die Zuweisung bestehen, was nicht das gewünschte Verhalten von OP ist. Markus Kuhn hat die richtige Version dieser Antwort.
EAJ