Variablenzuweisungen wirken sich auf die aktuelle laufende Shell aus

8

Beim Schreiben von Code habe ich festgestellt, dass diese Zeile:

$ TZ="America/Los_Angeles"       date; echo "$TZ"
Thu Dec 24 14:39:15 PST 2015

Gibt korrekt die tatsächliche Zeit in "Los Angeles" an und dass der Wert der Variablen TZnicht beibehalten wird. Alles wie erwartet.

Mit dieser Zeile, mit der ich bisher einige Formate erweitert habe und die im Wesentlichen dasselbe ausführt, bleibt jedoch der Wert von TZ erhalten:

TZ="America/Los_Angeles" eval  date; echo "$TZ"
Thu Dec 24 14:41:34 PST 2015
America/Los_Angeles

Nach mehreren weiteren Tests stellte ich fest, dass dies nur in einigen Schalen geschieht. Es passiert in dash, ksh aber nicht in bash oder zsh.

Q's

Die Frage (n) sind:

  • Warum bleibt der Wert von TZ in der vorliegenden Hülle erhalten?
  • Wie könnte dies vermieden / kontrolliert werden (wenn möglich)?

Zusätzlich.

Ich habe Tests in mehreren Schalen mit diesen beiden Zeilen durchgeführt:

myTZ="America/Los_Angeles"
unset TZ; { TZ="$myTZ"      date; } >/dev/null; echo -n "  direct $TZ"
unset TZ; { TZ="$myTZ" eval date; } >/dev/null; echo    "  evaled $TZ"

Und das Ergebnis:

/bin/ash        :   direct   evaled America/Los_Angeles
/bin/dash       :   direct   evaled America/Los_Angeles
/bin/sh         :   direct   evaled America/Los_Angeles
/bin/bash       :   direct   evaled
/bin/ksh93      :   direct   evaled America/Los_Angeles
/bin/lksh       :   direct   evaled America/Los_Angeles
/bin/mksh       :   direct   evaled America/Los_Angeles
/bin/zsh        :   direct   evaled
/bin/zsh4       :   direct   evaled 

Der TZ-Wert wirkt sich auf die laufende Shell in allen Shells außer Bash und Zsh aus.


quelle

Antworten:

6

Wie Sie festgestellt haben, ist dies ein spezifiziertes Verhalten. Aber es macht auch Sinn.

Der Wert wird in der Shell-Umgebung aus demselben Grund beibehalten, aus dem der Wert anderer Umgebungsvariablen von anderen Befehlen beibehalten wird, wenn Sie Definitionen ihren Befehlszeilen voranstellen - Sie legen die Variablen in ihrer Umgebung fest.

Die speziellen Buildins sind im Allgemeinen die intrinsischste Variante in jeder Shell - sie evalsind im Wesentlichen ein zugänglicher Name für den Parser der Shell, setverfolgen und konfigurieren Shell-Optionen und Shell-Parameter, return// break/ continuesteuern den Regelungsfluss, trapverarbeiten Signale, execöffnen / schließen Dateien. Dies sind alles grundlegende Dienstprogramme - und werden normalerweise mit kaum vorhandenen Verpackungen über dem Fleisch und den Kartoffeln Ihrer Schale implementiert.

Das Ausführen der meisten Befehle umfasst eine mehrschichtige Umgebung - eine Subshell-Umgebung (die nicht unbedingt ein separater Prozess sein muss) -, die Sie beim Aufrufen der speziellen integrierten Funktionen nicht erhalten. Wenn Sie also die Umgebung für einen dieser Befehle festlegen, legen Sie die Umgebung für Ihre Shell fest. Weil sie im Grunde Ihre Shell darstellen.

Aber sie sind nicht die einzigen Befehle, die die Umgebung auf diese Weise beibehalten - Funktionen tun dasselbe. Und Fehler verhalten sich bei speziellen integrierten Funktionen anders - versuchen Sie es cat <doesntexistund versuchen Sie es dann exec <doesntexistoder sogar nur, : <doesntexistund während der catBefehl sich beschwert, wird die execoder :eine POSIX-Shell beendet. Gleiches gilt für Erweiterungsfehler in der Befehlszeile. Sie sind im Grunde die Hauptschleife .

Diese Befehle nicht haben , um Umwelt zu bewahren - einige Muscheln ihre Einbauten einpacken fester als andere, setzen weniger der Kernfunktionalität und fügen Sie mehr Puffer zwischen dem Programmiergerät und die Schnittstelle. Dieselben Muscheln sind möglicherweise auch etwas langsamer als andere. Auf jeden Fall erfordern sie viele nicht standardmäßige Anpassungen, damit sie den Spezifikationen entsprechen. Und überhaupt ist es nicht so, als wäre das eine schlechte Sache:

fn(){ bad_command || return=$some_value return; }

Das Zeug ist einfach . Wie sonst würden Sie die Rückkehr von bad_commandso einfach bewahren, ohne eine Reihe zusätzlicher Umgebungen festlegen zu müssen und dennoch Aufgaben unter bestimmten Bedingungen auszuführen?

arg=$1 shift; x=$y unset y

Das Zeug funktioniert auch. In-Place-Swaps sind einfacher.

IFS=+  set -- "$IFS" x y z
x="$*" IFS=$1 shift
echo "${x#"$IFS"}" "$*"

+x+y+z x y z

...oder...

expand(){
    PS4="$*" set -x "" "$PS4" 
    { $1; }  2>&1
    PS4=$2   set +x
}   2>/dev/null

x='echo kill my computer; $y'
y='haha! just kidding!' expand "${x##*[\`\(]*}"

... ist eine andere, die ich gerne benutze ...

echo kill my computer; haha! just kidding!
mikeserv
quelle
@BinaryZebra - aber der Punkt ist, dass sie nicht anders funktionieren - wenn Sie Variablen für einen anderen Befehl festlegen, bleiben sie in der Umgebung dieser anderen ausführbaren Datei erhalten. Wenn Sie Variablen in der Umgebung Ihrer Shell festlegen, bleiben diese ebenfalls bestehen.
Mikeserv
3

Es stellt sich heraus, dass es einen ganz bestimmten Grund für dieses Verhalten gibt.
Die Beschreibung dessen, was passiert, ist etwas länger.

Nur Aufgaben.

Eine Befehlszeile, die (nur) aus Zuweisungen besteht, legt die Variablen für diese Shell fest.

$ unset a b c d
$ a=b c=d
$ echo "<$a::$c>"
<b::d>

Der Wert der zugewiesenen Variablen bleibt erhalten.

Externer Befehl.

Zuweisungen vor einem externen Befehlssatz Variablen nur für diese Shell:

$ unset a b c d
$ a=b c=d bash -c 'echo "one:|$c|"'; echo "two:<$c>"
one:|d|
two:<>

Und ich meine "extern" als jeden Befehl, der in PATH gesucht werden muss.

Dies gilt auch für normale integrierte Funktionen (wie z. B. CD):

$ unset a b c d; a=b c=d cd . ; echo "<$a::$c>"
<::>

Bis hierher ist alles so, wie es normalerweise erwartet wird.

Spezielle Einbauten.

Für spezielle integrierte Funktionen erfordert POSIX jedoch, dass die Werte für diese Shell festgelegt werden .

  1. Mit speziellen integrierten Dienstprogrammen angegebene Variablenzuweisungen bleiben auch nach Abschluss des integrierten Dienstprogramms wirksam.
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>

Ich benutze einen Aufruf, um shanzunehmen, dass shes sich um eine POSIX-kompatible Shell handelt.

Dies wird normalerweise nicht verwendet.

Dies bedeutet, dass Zuweisungen, die vor einer dieser Listen spezieller Einbauten platziert werden, die zugewiesenen Werte in der aktuellen laufenden Shell beibehalten müssen:

break : continue . eval exec exit export 
readonly return set shift times trap unset

Dies geschieht, wenn eine Shell gemäß der POSIX-Spezifikation funktioniert.

Fazit:

Es ist möglich, Variablen für nur einen Befehl, einen beliebigen Befehl, festzulegen, indem sichergestellt wird, dass der Befehl nicht speziell integriert ist. Der Befehl commandist regelmäßig eingebaut. Es weist die Shell nur an, einen Befehl zu verwenden, keine Funktion. Diese Zeile funktioniert in allen Shells (außer ksh93):

$ unset a b c d; a=b c=d command eval 'f=g'; echo "<$a::$c::$f>"
<::::g>

In diesem Fall werden die Variablen a und b für die Umgebung des Befehlsbefehls festgelegt und danach verworfen.

Stattdessen bleiben die zugewiesenen Werte erhalten (außer bash und zsh):

$ unset a b c d; a=b c=d eval 'f=g'; echo "<$a::$c::$f>"
<b::d::g>

Beachten Sie, dass die Zuweisung nach der Auswertung in einfachen Anführungszeichen steht, um sie vor unerwünschten Erweiterungen zu schützen.

Also: Um Variablen in der Befehlsumgebung zu platzieren, verwenden Sie command eval:


quelle