Dlaczego źródło lib / * nie działa?

11

Mam mały program, który zawiera następującą strukturę folderów:

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

Każdy plik zawiera jedną funkcję, z której korzystam main.sh.

main.sh:

source lib/*

get_products
clean_products
make_index
test_index

Powyżej dwie pierwsze funkcje działają, ale dwie pozostałe nie.

Jednak jeśli zastąpię source lib/*:

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

Wszystko działa zgodnie z oczekiwaniami.

Czy ktoś wie, dlaczego source lib/*nie działa zgodnie z oczekiwaniami?

Philip Kirkbride
źródło
2
Nie odpowiadając na pytanie, jeśli chcesz to zrobić w jednej linijce, sprawdź, /etc/bashrcjak używa forpętli do radzenia sobie /etc/profile.d/*.sh. Jeśli ufasz, zawartość lib/można sprowadzić do jednej linijki:for i in lib/*.sh; do . "$i"; done
Rich

Odpowiedzi:

21

Wbudowane oprogramowanie Bash sourcezajmuje tylko jedną nazwę pliku:

source filename [arguments]

Wszystko poza pierwszym parametrem staje się parametrem pozycyjnym dla filename.

Prosta ilustracja:

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

Pełna wydajność 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.

(Dotyczy to również wbudowanego równoważnego „źródła kropek”, .które, warto zauważyć, jest sposobem POSIX, a zatem bardziej przenośnym.)

Jeśli chodzi o pozornie sprzecznych zachowań widzisz można spróbować uruchomić main.sh po zrobieniu set -x. Sprawdzanie, jakie instrukcje są wykonywane i kiedy może dostarczyć wskazówek.

Warstwa B.
źródło
7

Dokumentacja Bash wskazuje, że sourcedziała na jednej nazwie pliku :

. (okres)

. nazwa pliku [argumenty]

Odczytywanie i wykonywanie poleceń z argumentu nazwa pliku w bieżącym kontekście powłoki. Jeśli nazwa pliku ...

A kod źródłowy ... dla źródła ... tworzy kopię zapasową:

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

Gdzie source_filejest zdefiniowane, evalfile.caby zadzwonić _evalfile:

rval = _evalfile (filename, flags);

i _evalfileotwiera tylko jeden plik:

fd = open (filename, O_RDONLY);
Jeff Schaller
źródło
5

Uzupełniając użyteczną odpowiedź warstwy b , sugerowałbym nigdy nie używać chciwego rozszerzenia glob, jeśli nie jesteś pewien, czy istnieją pliki typu, które próbują się rozwinąć.

Kiedy zrobiłeś poniżej, istnieje możliwość, że plik (bez .shrozszerzenia) to tylko plik tymczasowy zawierający szkodliwe polecenia (np. rm -rf *), Które można wykonać (zakładając, że mają uprawnienia do wykonywania)

source lib/*

Dlatego zawsze wykonuj ekspansję glob z odpowiednim ustawionym zestawem, w twoim przypadku możesz po prostu zapętlić *.shsame pliki

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

W tym przypadku [ -f "$globFile" ] || continuezadbałby o powrót z pętli, jeśli w bieżącym folderze nie pasuje żaden wzorzec globalny, tj. Odpowiednik opcji rozszerzonej powłoki nullglobw bashpowłoce.

Inian
źródło
Zastosowanie podstawienia procesu przy pomocy catrównież działałoby:source <(cat lib/*.sh)
Xophmeister
@Xophmeister, ... dla bardziej ograniczonej wartości „pracy”. Jeśli próbowałeś debugować za pomocą set -xa, PS4który umieszcza BASH_SOURCEi LINENOw twoich logach, nie możesz już zobaczyć, z którego pliku i linii pochodzi dana komenda.
Charles Duffy
2
@Xophmeister, ... również skrypt może zwierać jego wykonanie return. Zgodnie z tą praktyką każde wykonanie skryptu uniemożliwiłoby wykonanie wszystkich kolejnych.
Charles Duffy
1
Jest to bardzo zbliżone do tego, jak to się robi /etc/bashrcpodczas przetwarzania /etc/profile.d/*.sh.
Bogaty