Warum funktioniert source lib / * nicht?

11

Ich habe ein kleines Programm, das die folgende Ordnerstruktur enthält:

- main.sh
- lib/
  - clean.sh
  - get.sh
  - index.sh
  - test.sh

Jede Datei enthält eine einzelne Funktion, in der ich verwende main.sh.

main.sh::

source lib/*

get_products
clean_products
make_index
test_index

Oben funktionieren die ersten beiden Funktionen, die zweiten beiden jedoch nicht.

Wenn ich jedoch ersetze source lib/*durch:

source lib/get.sh
source lib/clean.sh
source lib/index.sh
source lib/test.sh

Alles funktioniert wie erwartet.

Weiß jemand, warum source lib/*nicht wie erwartet funktioniert?

Philip Kirkbride
quelle
2
Wenn Sie die Frage nicht beantworten und sie in einem Einzeiler ausführen möchten, prüfen Sie, /etc/bashrcwie eine forSchleife verwendet wird /etc/profile.d/*.sh. Wenn Sie vertrauen, kann der Inhalt lib/auf einen for i in lib/*.sh; do . "$i"; done
Rich

Antworten:

21

Bashs sourceeingebauter Dateiname hat nur einen einzigen Dateinamen:

source filename [arguments]

Alles, was über den ersten Parameter hinausgeht, wird zu einem Positionsparameter für filename.

Eine einfache Illustration:

$ cat myfile
echo "param1: $1"
$ source myfile foo
param1: foo

Volle Ausgabe von help source

source: source filename [arguments]

Execute commands from a file in the current shell.

Read and execute commands from FILENAME in the current shell.  The
entries in $PATH are used to find the directory containing FILENAME.
If any ARGUMENTS are supplied, they become the positional parameters
when FILENAME is executed.

Exit Status:
Returns the status of the last command executed in FILENAME; fails if
FILENAME cannot be read.

(Dies gilt auch für die entsprechende eingebaute "Punktquelle", .die, wie erwähnenswert, auf POSIX-Weise und damit portabler ist.)

Was das scheinbar widersprüchliche Verhalten betrifft, können Sie versuchen, main.sh nach dem Ausführen auszuführen set -x. Zu sehen, welche Anweisungen wann ausgeführt werden, kann einen Hinweis geben.

B Schicht
quelle
7

Die Bash-Dokumentation gibt an, dass sourceein einzelner Dateiname verwendet wird :

. (eine Periode, ein Zeitabstand)

. Dateiname [Argumente]

Lesen und Ausführen von Befehlen aus dem Dateinamenargument im aktuellen Shell-Kontext. Wenn Dateiname ...

Und der Quellcode ... für die Quelle ... sichert dies:

result = source_file (filename, (list && list->next));

Wo source_fileist definiert inevalfile.c um aufzurufen _evalfile:

rval = _evalfile (filename, flags);

und _evalfileöffnet nur eine einzige Datei:

fd = open (filename, O_RDONLY);
Jeff Schaller
quelle
5

Ergänzen zur nützlichen Antwort von b-layer würde ich empfehlen, niemals eine gierige Glob-Erweiterung zu verwenden, wenn Sie sich nicht sicher sind, ob die Dateien des Typs, der erweitert werden soll, vorhanden sind.

Wenn Sie dies unten getan haben, besteht die Möglichkeit, dass eine Datei (ohne .shErweiterung) nur eine temporäre Datei enthält, die einige schädliche Befehle enthält (z. B. rm -rf *), die ausgeführt werden könnten (vorausgesetzt, sie haben Ausführungsberechtigungen).

source lib/*

Führen Sie die Glob-Erweiterung also immer mit dem richtigen gebundenen Satz durch, in Ihrem Fall, obwohl Sie nur *.shDateien alleine durchlaufen könnten

for globFile in lib/*.sh; do
    [ -f "$globFile" ] || continue
    source "$globFile"
done

Hier [ -f "$globFile" ] || continuewürde sich das um das Zurückkehren aus der Schleife kümmern, wenn im aktuellen Ordner kein Glob-Muster übereinstimmt, dh äquivalent zu den erweiterten Shell-Optionen nullglobin der bashShell.

Inian
quelle
Die Verwendung der Prozesssubstitution mit catwürde auch funktionieren:source <(cat lib/*.sh)
Xophmeister
@Xophmeister, ... für einen begrenzten Wert für "Arbeit". Wenn Sie versucht zu debuggen mit set -xund PS4dass Puts BASH_SOURCEund LINENOin Ihren Protokollen, könnten Sie nicht mehr , die Datei sehen und ein gegebener Befehl herkommt auskleiden.
Charles Duffy
2
@Xophmeister, ... auch ein Skript kann seine Ausführung mit kurzschließen return. Nach dieser Vorgehensweise würde jedes Skript, das dies tut, die Ausführung aller folgenden Skripte verhindern .
Charles Duffy
1
Dies ist ziemlich nahe daran, wie es /etc/bashrcbei der Verarbeitung gemacht wird /etc/profile.d/*.sh.
Rich