Gibt es schließlich einen Unterschied zwischen "." Und "source" in bash?

37

Ich suchte nach dem Unterschied zwischen dem "." Die in "source" eingebauten Befehle und einige Quellen (z. B. in dieser Diskussion und in der bash- Manpage) legen nahe, dass diese genau gleich sind.

Nach einem Problem mit Umgebungsvariablen führte ich jedoch einen Test durch. Ich habe eine Datei erstellt, die Folgendes testenv.shenthält:

#!/bin/bash
echo $MY_VAR

In der Eingabeaufforderung habe ich Folgendes ausgeführt:

> chmod +x testenv.sh
> MY_VAR=12345
> ./testenv.sh

> source testenv.sh
12345
> MY_VAR=12345 ./testenv.sh
12345

[Beachten Sie, dass das 1. Formular eine leere Zeichenfolge zurückgegeben hat]

Dieses kleine Experiment legt also den Schluss nahe, dass es doch einen Unterschied gibt , bei dem für den Befehl "source" die untergeordnete Umgebung alle Variablen von der übergeordneten erbt, bei der Variable "." Es tut nicht.

Vermisse ich etwas oder ist dies eine undokumentierte / veraltete Funktion von bash ?

[GNU Bash, Version 4.1.5 (1) -Release (x86_64-pc-linux-gnu)]

ysap
quelle

Antworten:

67

Kurze Antwort

In Ihrer Frage verwendet der zweite Befehl weder die .eingebaute Shell noch die sourceeingebaute. Stattdessen führen Sie das Skript tatsächlich in einer separaten Shell aus, indem Sie es wie bei jeder anderen ausführbaren Datei über den Namen aufrufen. Dies gibt ihm einen separaten Variablensatz ( wenn Sie jedoch eine Variable in die übergeordnete Shell exportieren , ist sie eine Umgebungsvariable für einen beliebigen untergeordneten Prozess und wird daher in die Variablen einer untergeordneten Shell aufgenommen ). Wenn Sie das /zu einem Leerzeichen ändern , würde das es mit dem .eingebauten ausführen , was äquivalent zu ist source.

Erweiterte Erklärung

Dies ist die Syntax der integrierten sourceShell, die den Inhalt eines Skripts in der aktuellen Shell (und damit mit den Variablen der aktuellen Shell) ausführt:

source testenv.sh

Dies ist die Syntax der .integrierten Funktion, die das Gleiche bewirkt wie source:

. testenv.sh

Diese Syntax führt das Skript jedoch als ausführbare Datei aus und startet eine neue Shell, um es auszuführen:

./testenv.sh

Das nutzt das .eingebaute nicht . Ist vielmehr .Teil des Pfads zu der Datei, die Sie ausführen. Im Allgemeinen können Sie jede ausführbare Datei in einer Shell ausführen, indem Sie sie mit einem Namen aufrufen, der mindestens ein /Zeichen enthält. Am ./einfachsten ist es also, eine Datei im aktuellen Verzeichnis voranzustellen. Wenn sich das aktuelle Verzeichnis nicht in Ihrem befindet PATH, können Sie das Skript nicht mit dem Befehl ausführen testenv.sh. Dies soll verhindern, dass Benutzer versehentlich Dateien im aktuellen Verzeichnis ausführen, wenn sie beabsichtigen, einen Systembefehl oder eine andere Datei auszuführen, die in einem in der PATHUmgebungsvariablen aufgelisteten Verzeichnis vorhanden ist.

Da das Ausführen einer Datei nach Namen (anstatt mit sourceoder .) in einer neuen Shell erfolgt, verfügt sie über einen eigenen Satz von Shell-Variablen. Die neue Shell erbt die Umgebungsvariablen vom aufrufenden Prozess (in diesem Fall von Ihrer interaktiven Shell), und diese Umgebungsvariablen werden zu Shellvariablen in der neuen Shell. Damit eine Shell-Variable an die neue Shell übergeben werden kann, muss eine der folgenden Bedingungen erfüllt sein:

  1. Die Shell-Variable wurde exportiert, sodass es sich um eine Umgebungsvariable handelt. Verwenden Sie dazu die exporteingebaute Shell. In Ihrem Beispiel können Sie export MY_VAR=12345die Variable in einem Schritt festlegen und exportieren. Wenn sie bereits festgelegt ist, können Sie sie einfach verwenden export MY_VAR.

  2. Die Shell-Variable wird explizit für den von Ihnen ausgeführten Befehl festgelegt und übergeben, sodass sie für die Dauer des ausgeführten Befehls eine Umgebungsvariable ist. Dies führt normalerweise dazu , dass:

    MY_VAR=12345 ./testenv.sh

    Wenn MY_VARein Shell - Variable ist , die nicht exportiert hat, kann man sogar läuft testenv.shmit MY_VARgeben als Umgebungsvariable durch sie selbst einstellen :

    MY_VAR="$MY_VAR" ./testenv.sh

./ Syntax für Skripte erfordert eine Hashbang-Zeile (korrekt)

Übrigens: Wenn Sie eine ausführbare Datei wie oben beschrieben aufrufen (und nicht mit den integrierten .oder sourceShell-Funktionen), wird das zum Ausführen verwendete Shell-Programm in der Regel nicht von der Shell bestimmt, von der aus Sie sie ausführen . Stattdessen:

  • Bei Binärdateien kann der Kernel so konfiguriert sein, dass er Dateien dieses bestimmten Typs ausführt. Es untersucht die ersten beiden Bytes der Datei auf eine "magische Zahl" , die angibt, um welche Art von ausführbarer Binärdatei es sich handelt. So können ausführbare Binärdateien ausgeführt werden.

    Dies ist natürlich äußerst wichtig, da ein Skript ohne eine Shell oder einen anderen Interpreter, der eine ausführbare Binärdatei ist, nicht ausgeführt werden kann! Außerdem sind viele Befehle und Anwendungen kompilierte Binärdateien und keine Skripte.

    ( #!Ist die Textdarstellung der "magischen Zahl", die eine ausführbare Textdatei angibt.)

  • Bei Dateien, die in einer Shell oder einer anderen interpretierten Sprache ausgeführt werden sollen, sieht die erste Zeile folgendermaßen aus:

    #!/bin/sh

    /bin/shkann durch eine andere Shell oder einen anderen Interpreter ersetzt werden, mit dem das Programm ausgeführt werden soll. Ein Python-Programm könnte beispielsweise mit der folgenden Zeile beginnen:

    #!/usr/bin/python

    Diese Zeilen werden Hashbang, Shebang und eine Reihe anderer ähnlicher Namen genannt. Siehe diesen FOLDOC-Eintrag , diesen Wikipedia-Artikel und Wird #! / Bin / sh vom Interpreter gelesen? für mehr Informationen.

  • Wenn eine Textdatei als ausführbar markiert ist und Sie sie von Ihrer Shell aus ausführen (wie ./filename), aber nicht damit beginnen #!, kann der Kernel sie nicht ausführen. Wenn dies jedoch geschehen ist, wird Ihre Shell versuchen , es auszuführen, indem Sie ihren Namen an eine andere Shell übergeben. Es gibt einige Anforderungen gestellt auf , was Shell das ist ( „die Schale wird ein Befehl entspricht mit einer Shell ausführen aufgerufen ...“ ). In der Praxis führen einige Shells - einschließlich bash* - eine andere Instanz von sich selbst aus, während andere sie verwenden/bin/sh. Ich empfehle dringend, dass Sie dies vermeiden und stattdessen eine Hashbang-Zeile verwenden (oder das Skript ausführen, indem Sie es an den gewünschten Interpreter übergeben, z bash filename. B. ).

    * GNU Bash-Handbuch , 3.7.2 Befehlssuche und -ausführung: "Wenn diese Ausführung fehlschlägt, weil die Datei nicht im ausführbaren Format vorliegt und es sich nicht um ein Verzeichnis handelt, wird angenommen, dass es sich um ein Shell-Skript handelt, und die Shell führt es wie beschrieben aus in Shell-Skripten . "

Eliah Kagan
quelle
2
Was ich nützlich fand, sourceist, dass die Funktionen von Bash verfügbar wurden, ohne dass sie neu geladen oder gestartet werden mussten. Beispiel #!/bin/bash function olakease {echo olakease;}. Sobald Sie es mit laden, können source file.shSie direkt olakeasevon Bash anrufen . Das gefällt mir sehr. Source führt dann viele Dinge aus, der Punkt .ist nur für die Ausführung und ist so etwas wie usebash file.sh
erm3nda
2
@ erm3nda .hat auch dieses Verhalten: . file.shund source file.shmacht genau dasselbe, einschließlich der in definierten Haltefunktionen file.sh. (Vielleicht denken Sie darüber nach ./file.sh, was anders ist. Aber das verwendet nicht das .eingebaute, sondern .ist Teil des Pfades.)
Eliah Kagan
Oh !, ich habe die. [Space] -Datei nicht sorgfältig gelesen. Vielen Dank.
erm3nda
13

Ja, dir fehlt etwas.

Ich denke, Sie verwirren das "." das heißt aktuelles Verzeichnis, wie in ./testenv.shund das '.' das heißt source(das ist ein eingebauter Befehl). Also in dem Fall, wenn '.' Das heißt, sourcees wäre . ./testenv.sh. Sinn ergeben?

Also versuche folgendes:

MY_VAR=12345 
. ./testenv.sh
user1477
quelle
2
Das ./sagt ihm genau, wo die Datei, ohne die sie sich befindet, zuerst PATH durchschaut und dann das aktuelle Verzeichnis ausprobiert, wenn es es nicht gefunden hat. Wenn bash im POSIX-Modus ausgeführt wird und Sie keinen Pfad zur Datei (wie ./) angeben, wird nur in PATH gesucht und die Datei kann nicht gefunden werden, wenn sich das aktuelle Verzeichnis nicht in PATH befindet.
Geirha
@geirha Ja, du hast Recht, source(und .) wird tatsächlich $ PATH zuerst überprüfen, auch wenn sie nicht wirklich läuft das Skript im üblichen Sinne. Mein (früherer) Kommentar war falsch.
Eliah Kagan
Kurz und auf den Punkt +1
David Morales