Pourquoi la source lib / * ne fonctionne-t-elle pas?

11

J'ai un petit programme qui contient la structure de dossiers suivante:

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

Chaque fichier contient une seule fonction que j'utilise dans main.sh.

main.sh:

source lib/*

get_products
clean_products
make_index
test_index

Dans ce qui précède, les deux premières fonctions fonctionnent mais pas les deux autres.

Pourtant, si je remplace source lib/*par:

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

Tout fonctionne comme prévu.

Quelqu'un sait pourquoi source lib/*ne fonctionne pas comme prévu?

Philip Kirkbride
la source
2
Si vous ne répondez pas à la question, si vous voulez le faire dans une seule ligne, regardez /etc/bashrccomment il utilise une forboucle pour traiter /etc/profile.d/*.sh. Si vous faites confiance, son contenu lib/peut être réduit à une ligne:for i in lib/*.sh; do . "$i"; done
Rich

Réponses:

21

La fonction sourceintégrée de Bash ne prend qu'un seul nom de fichier:

source filename [arguments]

Tout ce qui dépasse le premier paramètre devient un paramètre positionnel pour filename.

Une illustration simple:

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

Sortie complète de 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.

(Cela s'applique également à la fonction intégrée "dot source" équivalente .qui, il convient de le noter, est la méthode POSIX et donc plus portable.)

En ce qui concerne le comportement apparemment contradictoire que vous voyez, vous pouvez essayer d'exécuter main.sh après l'avoir fait set -x. Voir quelles instructions sont exécutées et quand peut fournir un indice.

Couche B
la source
7

La documentation de Bash indique que sourcefonctionne sur un seul nom de fichier :

. (une période)

. nom de fichier [arguments]

Lisez et exécutez des commandes à partir de l' argument filename dans le contexte shell actuel. Si le nom de fichier ...

Et le code source ... pour la source ... le sauvegarde:

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

source_fileest défini evalfile.cpour appeler _evalfile:

rval = _evalfile (filename, flags);

et _evalfilen'ouvre qu'un seul fichier:

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

En complément de la réponse utile de la couche b , je suggérerais de ne jamais utiliser une extension glob gourmande si vous ne savez pas si les fichiers du type essayant de se développer sont là.

Lorsque vous l'avez fait ci-dessous, il est possible qu'un fichier (sans .shextension) soit juste un fichier temporaire contenant des commandes nuisibles (par exemple rm -rf *) qui pourraient être exécutées (en supposant qu'elles disposent des autorisations d'exécution)

source lib/*

Donc, faites toujours l'expansion glob avec un ensemble de limites approprié, dans votre cas, bien que vous puissiez simplement boucler sur des *.shfichiers seuls

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

Ici, le [ -f "$globFile" ] || continueprendrait en charge le retour de la boucle si aucun modèle de glob ne correspond dans le dossier actuel, c'est-à-dire l'équivalent des options de shell étendues nullglobdans bashshell.

Inian
la source
Utiliser la substitution de processus avec catfonctionnerait également:source <(cat lib/*.sh)
Xophmeister
@Xophmeister, ... pour une valeur plus limitée pour "travail". Si vous avez essayé de déboguer avec set -xet PS4qui place BASH_SOURCEet LINENOdans vos journaux, vous ne pouvez plus voir de quel fichier et de quelle ligne provient une commande donnée.
Charles Duffy
2
@Xophmeister, ... aussi, un script peut court-circuiter son exécution avec return. Suivant cette pratique, tout script faisant cela empêcherait tous les suivants de s'exécuter.
Charles Duffy
1
C'est assez proche de la façon dont cela se fait /etc/bashrclors du traitement /etc/profile.d/*.sh.
Rich