Warum ist das Setzen einer Variablen vor einem Befehl in der Bash zulässig?

68

Ich habe gerade mehrere Antworten gefunden, zum Beispiel zum Parsen einer Textdatei mit Trennzeichen ... die das Konstrukt verwenden:

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

Wobei die IFSVariable vor dem readBefehl gesetzt wird.

Ich habe die Bash-Referenz gelesen , kann aber nicht herausfinden, warum dies legal ist.

Ich habe es versucht

$ x="once upon" y="a time" echo $x $y

von der Bash-Eingabeaufforderung wurde aber nichts wiedergegeben. Kann mir jemand sagen, wo diese Syntax in der Referenz definiert ist, mit der die IFS-Variable auf diese Weise festgelegt werden kann? Ist es ein Sonderfall oder kann ich etwas Ähnliches mit anderen Variablen tun?

Mike Lippert
quelle

Antworten:

69

Es steht unter Shell-Grammatik, Einfache Befehle (Hervorhebung hinzugefügt):

Ein einfacher Befehl ist eine Folge von optionalen Variablenzuweisungen, gefolgt von durch Leerzeichen getrennten Wörtern und Umleitungen , die von einem Steueroperator beendet werden. Das erste Wort gibt den auszuführenden Befehl an und wird als Argument Null übergeben. Die verbleibenden Wörter werden als Argumente an den aufgerufenen Befehl übergeben.

Sie können also eine beliebige Variable übergeben. Ihr echoBeispiel funktioniert nicht, da die Variablen an den Befehl übergeben und nicht in der Shell festgelegt werden. Die Shell wird erweitert $xund $y vor dem Aufrufen des Befehls. Das funktioniert zum Beispiel:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time
derobert
quelle
Vielen Dank! Ich habe viel gegoogelt, bevor ich gefragt habe, und habe versucht herauszufinden, wo in der Referenz stand, dass es kein Glück gibt. Ich versuche, Bash-Skripte besser zu schreiben, und Ihre Antwort hilft.
Mike Lippert
1
Hmm, ich denke, ich muss eine bessere Referenz finden, meine (siehe Link oben) sagt nicht, dass es keinen Shell-Grammatik-Abschnitt gibt.
Mike Lippert
1
@MikeLippert Siehe 3.7.4 in dieser Referenz ("Die Umgebung für einfache Befehle oder Funktionen kann vorübergehend erweitert werden, indem ihr Parameter zugewiesen werden"). Ich denke, dass die Referenz von einer älteren Version von Bash stammt. Ich lief gerade man bashauf meinem System ...
Derobert
29

Die definierten Variablen werden zu Umgebungsvariablen im gegabelten Prozess.

Wenn du läufst

A="b" echo $A

Dann expandiert bash zuerst $Ain ""und läuft dann

A="b" echo

Hier ist der richtige Weg:

x="once upon" y="a time" bash -c 'echo $x $y'

Beachten Sie die einfachen Anführungszeichen in bash -c, ansonsten haben Sie das gleiche Problem wie oben.

Ihr Schleifenbeispiel ist also zulässig, da der in Bash integrierte Befehl 'read' in seinen Umgebungsvariablen nach IFS sucht und findet ,. Deshalb,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

wird gedruckt TEST is and I is test

Als letztes wird, wie bei der Syntax, in einer for-Schleife eine Zeichenkette erwartet. Deshalb musste ich Backticks verwenden, um daraus einen Befehl zu machen. While-Schleifen erwarten jedoch Befehlssyntax, z IFS=, read xx yy zz.

Mike Fairhurst
quelle
1
Danke, ich habe ein bisschen mehr gelernt, als ich von Ihrer Antwort verlangt habe. Ich würde Ihre Antwort auch abstimmen, aber ich bin noch nicht erlaubt und ich habe die akzeptierte Antwort auf der ersten markiert.
Mike Lippert
1
Danke, das habe ich mir erhofft. Und eine Abstimmung ist weniger aussagekräftig als Ihre Wertschätzung zu hören!
Mike Fairhurst
Um Ihren Kommentar unter der ersten Codezeile zu verdeutlichen: Ja, mit der nicht gesetzten AVariablen wird bash $Azu einer leeren Zeichenfolge erweitert , aber um Verwirrung zu vermeiden, würde ich nicht verwenden, ""da der Code nicht äquivalent zu ist A="b" echo "". Es wird kein Argument dafür geben echo.
Pabouk
8

man bash

UMGEBUNG

[...] Die Umgebung für einen einfachen Befehl oder eine Funktion kann vorübergehend erweitert werden, indem Parameterzuweisungen vorangestellt werden, wie oben in PARAMETERS beschrieben. Diese Zuweisungsanweisungen wirken sich nur auf die Umgebung aus, die von diesem Befehl angezeigt wird.

Die Variablen werden vor der Variablenzuordnung erweitert. Aus dem offensichtlichen Grund var=xwürde das auch anders funktionieren, aber var=$othervarnicht. Dh Ihr $xwird benötigt, bevor es verfügbar ist. Das ist aber nicht das Hauptproblem. Das Hauptproblem besteht darin, dass die Befehlszeile nur von der Shell-Umgebung geändert werden kann, die Zuweisung jedoch nicht Teil der Shell-Umgebung wird.

Sie vermischen sich mit Funktionen: Sie möchten eine Befehlszeile ersetzen, aber die Variablendefinition in die Befehlsumgebung einfügen. Befehlszeilenersetzungen müssen von der Shell vorgenommen werden. Die Umgebung muss vom aufgerufenen Befehl explizit verwendet werden. Ob und wie dies geschieht, hängt vom Befehl ab.

Der Vorteil dieser Verwendung besteht darin, dass Sie die Umgebung für einen Unterprozess festlegen können, ohne die Shell-Umgebung zu beeinträchtigen.

x="once upon" y="a time" bash -c 'echo $x $y'

Funktioniert wie erwartet, da in diesem Fall beide Funktionen kombiniert werden: Die Befehlszeilenersetzung wird nicht von der aufrufenden Shell, sondern von der Subprozess-Shell durchgeführt.

Hauke ​​Laging
quelle
1
Es ist etwas subtiler, da das Beispiel auch funktioniert, x="once upon" y="a time" eval 'echo $x $y'wenn kein Unterprozess beteiligt ist, da evales sich um einen eingebauten Prozess handelt. Ich denke, das relevante Zitat aus der Manpage ist The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments. In Anbetracht des Beispiels der Frage muss es so sein, da reades sich auch um ein eingebautes handelt und es mit einem zeitlich veränderten Zustand arbeitet IFS.
David Ongaro
4

Der Befehl , den Sie bieten ist anders , weil $xund $yexpandiert werden , bevor der echoBefehl ausgeführt wird , so dass ihre Werte in der aktuellen Shell verwendet werden, nicht die Werte , die echoin seiner Umgebung sehen würde , wenn es zu sehen waren.

chepner
quelle
Wie kann etwas in der Befehlszeile erweitert werden, nachdem der Befehl ausgeführt wurde?
Hauke ​​Laging
Die Vorbefehlszuweisungen für xund ygelten für die Umgebung, echoin der sie ausgeführt werden, und nicht für die Umgebung, in der die zu echoerweiterenden Argumente erweitert werden. Denn IFS=, read xx yy zzdie gesamte Zeichenkette wird vom readBefehl ungeteilt gelesen . Dann wird die Zeichenfolge aufgeteilt entsprechend dem Wert der IFSmit den entsprechenden Teilen zugeordnet sind xx, yyund zz.
Chepner
Ich wollte nur darauf hinweisen, dass die Formulierung "erweitert, bevor der Befehl ausgeführt wird" wenig Sinn macht, da nach dem Start des Befehls nichts mehr erweitert wird. Außerdem: Haben Sie sich meine Antwort nicht einmal angeschaut oder glauben Sie trotzdem, dass ich eine Erklärung brauche, was passiert ...?
Hauke ​​Laging
1
Ich habe weder behauptet, dass Sie eine Erklärung brauchen, noch dass etwas erweitert werden kann, nachdem der Befehl ausgeführt wurde. Es ist jedoch richtig, dass bashzuerst die angegebene Befehlszeile analysiert wird, erkannt wird, dass zwei Variablenzuweisungen für die Umgebung des kommenden Befehls gelten, der auszuführende Befehl identifiziert wird ( echo), alle in den Argumenten gefundenen Parameter erweitert werden und dann der Befehl ausgeführt wird echomit den erweiterten Argumenten.
Chepner
Im Falle von war echoich nicht sicher, ob es die Variable "sehen" konnte, da es ein eingebauter Befehl ist und daher nicht in einer Subshell ausgeführt wird, die eine eigene Umgebung haben könnte. Aber ich habe es ausprobiert, evalwomit auch ein eingebaut ist und es ja weiß. ZB versuchen Sie, a=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $awas zeigt, dass anur innerhalb festgelegt wird eval, obwohl die PID gleich ist und die Umgebung während der Auswertung nicht verändert wird! (Um darauf zuzugreifen /proc, müssen Sie dies unter Linux ausführen.) Es scheint, dass Bash hier zusätzliche Magie ausübt.
David Ongaro
2

Ich gehe auf das Gesamtbild von " Warum ist es legal?"

Antwort: Damit Sie ein Programm aufrufen oder aufrufen können und für diesen Aufruf nur eine Variable mit einer bestimmten Variablen verwenden können.

Beispiel: Sie haben einen Parameter für eine Datenbankverbindung mit dem Namen 'db_connection' und übergeben normalerweise 'test' als Namen für Ihre Testdatenbankverbindung. In der Tat können Sie es sogar als Standard festlegen, den Sie dann nicht explizit übergeben müssen. Manchmal möchten Sie jedoch mit der ci-Datenbank arbeiten. Sie übergeben den Parameter also als 'ci' und das aufgerufene Programm verwendet diesen Datenbankparameter als Namen der Datenbank, die für alle Datenbankaufrufe verwendet werden soll. Wenn Sie den Vorgang für den nächsten Lauf nicht wiederholen und nur das Programm aufrufen, wird die Variable auf den vorherigen Standardwert zurückgesetzt.

Michael Durrant
quelle
0

Sie können auch verwenden ;. Es wird vorher ausgewertet, weil es ein Befehlstrennzeichen ist.

x="once upon" y="a time"; echo $x $y
camabeh
quelle
1
Dadurch werden zwei Shell-Variablen erstellt, und im angegebenen Dienstprogramm werden keine Umgebungsvariablen erstellt. Das ist etwas ganz anderes. Es gibt auch den Nebeneffekt, dass die aktuelle Shell jetzt neue Variablen enthält.
Kusalananda