Warum erweitert Bash diese Variable nicht, wenn ich einem Befehl eine „einmalige Variablenzuweisung“ voranstelle?

Warum erweitert Bash diese Variable nicht, wenn ich einem Befehl eine „einmalige Variablenzuweisung“ voranstelle?

Wenn ich diesen Bash-Befehl ausführe und der Anweisung ein Präfix voranstelle, sodass die Variable fruitexistieren sollte, aber nur für die Dauer dieses Befehls:

$ fruit=apple echo $fruit

$

Das Ergebnis ist eine leere Zeile. Warum?

Um einen Kommentar von Wildcard zu zitieren aufdiese Frage:

Die Parametererweiterung wird von der Shell durchgeführt und die Variable „fruit“ ist keine Shell-Variable; sie ist nur eine Umgebungsvariable innerhalb der Umgebung des Befehls „echo“

Eine Umgebungsvariable ist immer noch eine Variable. Daher sollte sie doch sicher weiterhin für den Echo-Befehl verfügbar sein, oder?

Antwort1

Das Problem besteht darin, dass die aktuelle Shell die Variable zu früh erweitert. Sie ist in ihrem Kontext nicht festgelegt, sodass der echoBefehl kein Argument erhält. Der Befehl lautet also letztendlich:

$ fruit=apple echo

Hier ist ein Workaround, bei dem die Variable wegen der einfachen Anführungszeichen nicht zu früh erweitert wird:

$ fruit=apple sh -c 'echo $fruit'

Alternativ können Sie auch ein einzeiliges Shell-Skript verwenden, das demonstriert, dass die fruitVariable korrekt an den ausgeführten Befehl übergeben wird:

$ cat /tmp/echof
echo $fruit
$ /tmp/echof

$ fruit=apple /tmp/echof
apple
$ echo $fruit

$

Einige Kommentare, da diese Frage einige unerwartete Kontroversen und Diskussionen ausgelöst hat:

  • Die Tatsache, dass die Variable fruitbereits exportiert wurde oder nicht, hat keinen Einfluss auf das Verhalten. Entscheidend ist, welchen Wert die Variable in dem genauen Moment hat, in dem die Shell sie erweitert.
$ Export Obst=Banane
$ Obst = Apfel echo $ Obst
Banane
  • Die Tatsache, dass es sich bei dem echoBefehl um einen integrierten Befehl handelt, hat keinen Einfluss auf das OP-Problem. Es gibt jedoch Fälle, in denen die Verwendung integrierter Befehle oder Shell-Funktionen mit dieser Syntax unerwartete Nebenwirkungen hat, z. B.:
$ Export Obst=Banane
$ Obst = Apfel eval 'echo $Obst'
Apfel
$ echo $Obst
Apfel
  • Zwar gibt es eine Ähnlichkeit zwischendie hier gestellte Frageund das hier ist nicht genau das gleiche Problem. Bei dieser anderen Frage IFSist der temporäre Variablenwert noch nicht verfügbar, wenn die ShellWortspaltung ein anderervariable $varwhile hier, der temporäre fruitVariablenwert ist noch nicht verfügbar, wenn die ShellerweitertDieDasselbeVariable.

  • Es gibt auchdiese andere Fragewo der OP nach der Bedeutung der verwendeten Syntax fragt und genauer fragt: „Warum funktioniert das?“. Hier ist sich der OP der Bedeutung bewusst, meldet aber ein unerwartetes Verhalten und fragt nach dessen Ursache, d. h. „Warum funktioniert das nicht?“. Ok, nachdem ich den schlechten Screenshot, der bei der anderen Frage gepostet wurde, genauer gelesen habe, ist dort tatsächlich dieselbe Situation beschrieben ( BAZ=jake echo $BAZ), also ja, das ist schließlich ein Duplikat

Antwort2

Um dies richtig zu verstehen, differenzieren wir zunächstShell-VariablenausUmgebungsvariablen.

Umgebungsvariablen sind eine Eigenschaft, die ALLE Prozesse haben, egal ob sie sie intern verwenden oder nicht. Sogar sleep 10wenn sie ausgeführt werden, gibt es Umgebungsvariablen. So wie alle Prozesse eine PID (Prozesskennung), ein aktuelles Arbeitsverzeichnis (cwd), eine PPID (übergeordnete PID), eine Argumentliste (auch wenn sie leer ist) usw. haben, so haben auch alle Prozesse eine sogenannte „Umgebung“, die beim Aufspalten vom übergeordneten Prozess geerbt wird.

Aus Sicht eines Utility-Autors (jemand, der Code in C schreibt) haben Prozesse die Möglichkeit, Umgebungsvariablen zu setzen, zu löschen oder zu ändern. Aus Sicht eines Skript-Autors bieten die meisten Tools ihren Benutzern diese Möglichkeit jedoch nicht. Stattdessen verwenden Sie IhreHülseum die Prozessumgebung zu ändern, die dann übernommen wird, wenn der von Ihnen aufgerufene Befehl (externe Binärdatei) ausgeführt wird. (Die Umgebung Ihrer Shell selbst kann geändert und die Änderung übernommen werden, oder Sie können Ihre Shell anweisen, die Änderung nach dem Forking, aber vor der Ausführung des von Ihnen aufgerufenen Befehls vorzunehmen. In beiden Fällen wird die Umgebung übernommen. Wir werden uns beide Ansätze ansehen.)

Shell-Variablen sind eine andere Sache. Obwohlinnerhalb der SchaleSie verhalten sich gleich, der Unterschied besteht darin, dass bloße "Shell-Variablen" das Verhalten von Befehlen, die Sie aufrufen, nicht ändern oder beeinflussen.ausIhre Shell. In der richtigen Terminologie würde der Unterschied eigentlich etwas anders ausgedrückt werden;exportiertShell-Variablen werden Teil der Umgebung der Tools, die Sie aufrufen, während Shell-Variablen, dienichtexportiert werden, nicht. Ich finde es jedoch hilfreicher für die Kommunikation, Shell-Variablen, die nicht exportiert werden, als "Shell-Variablen" zu bezeichnen, und Shell-Variablen, dieSindals "Umgebungsvariablen" exportiert, da sieSindUmgebungsvariablen aus der Perspektive von Prozessen, die aus der Shell abgezweigt wurden.


Das ist eine Menge Text. Sehen wir uns einige Beispiele an und beschreiben wir, was passiert:

$ somevar=myfile
$ ls -l "$somevar"
-rw-r--r--  1 Myname  staff  0 May 29 19:12 myfile
$ 

In diesem Beispiel somevarist nur eine Shell-Variable, nichts Besonderes. ShellParametererweiterung(siehe LESS='+/Parameter Expansion' man bash) tritt aufVordie lsausführbare Datei wird tatsächlich geladen ("exec") und der lsBefehl (Prozess) wird nicht einmalsiehtdie Zeichenfolge „Dollarzeichen somevar“. Es sieht nur die Zeichenfolge „myfile“, interpretiert diese als Pfad zu einer Datei im aktuellen Arbeitsverzeichnis und ruft Informationen darüber ab und druckt sie aus.

Wenn wir export somevarvor dem Befehl ausführen, dann erscheint lsdie Tatsache, dass in dersomevar=myfileUmfelddes lsProzesses, aber das hat keine Auswirkungen, da der lsBefehl mit dieser Variable nichts macht. Um eineWirkungeiner Umgebungsvariablen müssen wir eine Umgebungsvariable auswählen, nach der der aufgerufene Prozess tatsächlich sucht und mit der er etwas tut.


bc: Einfacher Rechner

Es gibt vielleicht ein besseres Beispiel, aber das hier ist eines, das mir eingefallen ist und nicht zu kompliziert ist. Zunächst sollten Sie wissen, dass es sich bcum einen einfachen Rechner handelt, der mathematische Ausdrücke verarbeitet und berechnet. (Nachdem er den Inhalt aller Eingabedateien verarbeitet hat, verarbeitet er seine Standardeingabe. Ich werde für meine Beispiele nicht seine Standardeingabe verwenden; ich drücke einfach Strg-D, was in den Textausschnitten unten nicht angezeigt wird. Außerdem verwende ich, -qum bei jedem Aufruf eine Einführungsmeldung zu unterdrücken.)

Die Umgebungsvariable, die ich veranschaulichen werde, ist hier beschrieben man bc:

   BC_ENV_ARGS
      This is another mechanism to get arguments to bc.  The format is
      the  same  as  the  command line arguments.  These arguments are
      processed first, so any files listed in  the  environment  argu-
      ments  are  processed  before  any  command line argument files.
      This allows the user to set up "standard" options and  files  to
      be  processed at every invocation of bc.  The files in the envi-
      ronment variables would typically contain  function  definitions
      for functions the user wants defined every time bc is run.

Hier geht:

$ cat file1
5*5
$ bc -q file1
25
$ cat file2
6*7
8+9+10
$ bc -q file2
42
27
$ bc -q file1 file2
25
42
27
$

Dies dient nur zur Veranschaulichung, wie bces funktioniert. In jedem dieser Fälle musste ich Strg-D drücken, um „Ende der Eingabe“ zu signalisieren bc.

Übergeben wir nun eine UmgebungsvariabledirektZu bc:

$ BC_ENV_ARGS=file1 bc -q file2
25
42
27
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$

Beachten Sie, dass wir in diese Variable Folgendes eingeben:nichtspäter von einem echoBefehl aus sichtbar. Indem wir die Zuweisung als Teil desselben Befehls setzen (auch ohne Semikolon), setzen wir diese Variablenzuweisung alsTeilder Umgebung von bc– die Shell selbst, die wir ausführen, blieb davon unberührt.

Nun setzen wir BC_ENV_ARGSalsHülseVariable:

$ BC_ENV_ARGS=file1
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
42
27
$

Hier können Sie sehen, dass unser echoBefehl den Inhalt sehen kann, aber er ist nicht Teil der Umgebung und kann bcdaher bcnichts Besonderes damit tun.

bcWenn wir die Variable selbst in die Argumentliste von einfügen würden, würden wir natürlich Folgendes sehen:

$ bc -q "$BC_ENV_ARGS"
25
$ 

Aber hier ist es dasHülseErweitern der Variable, und dann file1ist das, was tatsächlich in bc's erscheintArgumentliste. Es wird also weiterhin als Shell-Variable und nicht als Umgebungsvariable verwendet.

Lassen Sie uns nun diese Variable „exportieren“, sodass sie sowohl eine Shell-Variable als auch eine Umgebungsvariable ist:

$ export BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25
42
27
$

Und hier können Sie sehen, dass file1vor verarbeitet wird file2, obwohl dies hier in der Befehlszeile nicht erwähnt wird. Es ist Teil der Shell-Umgebung und wird Teil der bcUmgebung von , wenn Sie diesen Prozess ausführen. Der Wert dieser Umgebungsvariable lautet alsovererbtund beeinflusst die bcFunktionsweise.

Wir können dies immer noch für jeden einzelnen Befehl überschreiben und es sogar auf einen leeren Wert zurücksetzen:

$ BC_ENV_ARGS= bc -q file2
42
27
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25 
42
27
$ 

Aber wie Sie sehen, bleibt die Variable in unserer Shell gesetzt und exportiert, sichtbar sowohl für die Shell selbst als auch für alle späteren bcBefehle, dienichtden Wert überschreiben. Er bleibt so, bis wir ihn „unexportieren“ oder „aufheben“. Ich mache Letzteres:

$ unset BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$ 

Ein weiteres Beispiel, bei dem eine weitere Shell erzeugt wird:

Geben Sie die folgenden Befehle nacheinander in Ihre Shell ein und betrachten Sie die Ergebnisse. Versuchen Sie, die Ergebnisse vorherzusagen, bevor Sie sie ausführen.

# fruit is not set
echo "$fruit"
sh -c 'echo "$fruit"'
# fruit is set as a shell variable in the current shell only
fruit=apple
echo "$fruit"
sh -c 'echo "$fruit"'
sh -c "echo $fruit" ### NOT advised for use in scripts, for illustration only
# fruit is exported, so it's accessible in current AND new processes
export fruit
echo "$fruit"
sh -c 'echo "$fruit"'
echo '$fruit' ### I threw this in to make sure you're not confused on quoting
# fruit is unset again
unset fruit
echo "$fruit"
sh -c 'echo "$fruit"'
# setting fruit directly in environment of single command but NOT in current shell
fruit=apple sh -c 'echo "$fruit"'
echo "$fruit"
fruit=apple echo "$fruit"
# showing current shell is unaffected by directly setting env of single command
fruit=cherry
echo "$fruit"
fruit=apricot sh -c 'echo "$fruit"'
echo "$fruit"
sh -c 'echo "$fruit"'

Und noch eine letzte, um es besonders knifflig zu machen: Können Sie die Ausgabe der folgenden nacheinander ausgeführten Befehle vorhersagen? :)

fruit=banana
fruit=orange sh -c 'fruit=lemon echo "$fruit"; echo "$fruit"; export fruit=peach'
echo "$fruit"

Bitte erwähnen Sie in den Kommentaren alle gewünschten Klarstellungen. Ich bin sicher, dass einige davon hilfreich sein könnten. Aber es sollte hoffentlich auch so, wie es ist, hilfreich sein.

Antwort3

Denn Erweiterungen auf der Kommandozeile finden vor Variablenzuweisungen statt.Der Standard sagt es:

Wenn ein bestimmter einfacher Befehl ausgeführt werden muss, müssen die folgenden Schritte ausgeführt werden:

  1. Die als Variablenzuweisungen erkannten Wörter werden [...] zur Verarbeitung in den Schritten 3 und 4 gespeichert.

  2. Die Wörter, die keine Variablenzuweisungen oder Umleitungen sind, müssen erweitert werden. [...]

  3. Umleitungen erfolgen wie unter „Umleitung“ beschrieben.

  4. Jede Variablenzuweisung muss vor der Wertzuweisung erweitert werden.

Beachten Sie die Reihenfolge: Im ersten Schritt werden nurGerettet, anschließend werden die weiteren Wörter expandiert und erst zum Schluss finden die Variablenzuweisungen statt.

Natürlich ist es sehr wahrscheinlich, dass der Standard das nur sagt, weil es schon immer so war und sie nur das bestehende Verhalten kodifiziert haben. Es sagt nichts über die Geschichte oder die Gründe dafür aus. Die Shell muss irgendwann Wörter identifizieren, die wie Zuweisungen aussehen (und sei es nur, um sie nicht als Teil des Befehls zu verwenden), also könnte es meiner Meinung nach auch in der anderen Reihenfolge funktionieren, zuerst Variablen zuweisen und dann alles auf der Befehlszeile erweitern. (oder einfach von links nach rechts machen...)

verwandte Informationen