Comment définir un script shell pour qu'il soit recherché, pas exécuté

40

Je définis un script shell qu'un utilisateur devrait sourceexécuter plutôt que d'exécuter.

Existe-t-il un moyen conventionnel ou intelligent d'indiquer à l'utilisateur que c'est le cas, par exemple via une extension de fichier?

Existe-t-il un code shell que je peux écrire dans le fichier lui-même, ce qui entraînera l'écho d'un message et sa fermeture s'il est exécuté à la place de la source, afin d'aider l'utilisateur à éviter cette erreur évidente?

algue
la source
1
Donc, si l'utilisateur est en train d'écrire un script shell d'une ligne x, qui ne contient que la commande . your-script-to-be-sourced, tout va bien, mais s'il veut l'exécuter, bash your-script-to-be-sourcedcela devrait être interdit? Quel est le but de cette restriction?
user1934428
8
@ user1934428 Bien sûr. Ceci est normal pour un script qui calcule un certain nombre de envvariables et les laisse comme sortie de script de facto . Un novice restera coincé pendant des jours avec le puzzle si vous lui permettez de s'exécuter.
Kubanczyk

Réponses:

45

En supposant que vous exécutiez bash, placez le code suivant au début du script que vous voulez extraire mais non exécuter:

if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
    echo "Hey, you should source this script, not execute it!"
    exit 1
fi

Sous bash, ${BASH_SOURCE[0]}contiendra le nom du fichier en cours de lecture par le shell, qu'il soit en cours d’exploitation ou non.

En revanche, $0est le nom du fichier en cours d’exécution.

-efteste si ces deux fichiers sont le même fichier. S'ils le sont, nous alertons l'utilisateur et sortons.

Ni sont -efni BASH_SOURCEPOSIX. While -efest pris en charge par ksh, yash, zsh et Dash, BASH_SOURCEnécessite bash. Enzsh , cependant, ${BASH_SOURCE[0]}pourrait être remplacé par ${(%):-%N}.

John1024
la source
2
Tout simplement echo "Usage: source \"$myfile\""
kubanczyk
6
@kubanczyk sourcen'est pas portable. Considérant que cette réponse est spécifique à Bash, ce n’est pas si grave, mais c’est une bonne habitude d’utiliser le portable.
gronostaj
33

Un fichier non-exécutable peut être recherché mais non exécuté. Par conséquent, en tant que première ligne de défense, le fait de ne pas définir l'indicateur d'exécutable devrait être un bon indice ...

Edit: astuce que je viens de trébucher: faire du shebang un exécutable qui n’est pas un interpréteur de shell, /bin/falsefait que le script retourne une erreur (rc! = 0)

#!/bin/false "This script should be sourced in a shell, not executed directly"
xénoïde
la source
7
Cependant, un fichier non-exécutable peut toujours être exécuté via bash somefile.sh...
twalberg
1
Si vous savez que vous pouvez l'exécuter avec bash(vd, perl, python, awk ...), alors vous avez regardé la source et vu le commentaire disant de ne pas le faire :)
xenoid
1
Les scripts Perl sont généralement nommés somefile.plet Python somefile.py, alors non, je n'ai probablement pas lu les commentaires (quels sont-ils, même, de toute façon?) Et ils bash somefile.shsont plus courts à taper que chmod +x somefile.sh; ./somefile.sh...
twalberg
De plus, certains shells de type Bourne, y compris bash, essaieront d'abord execveun fichier, mais si cela échoue, ils l'inspectent manuellement et l'interprètent manuellement #!et l'invoquent via cet interpréteur: il s'agit d'un héritage de l'époque où il #!était purement utilisateur. convention, au lieu d’être gérée par le noyau lui-même. Du bash moins, je pense que cela ne sera pas le cas pour les fichiers non exécutables, mais je ne sais pas s'il est portable d'attendre un comportement aussi sain d'esprit de tous les shells à partir desquels l'utilisateur pourrait appeler le script.
mtraceur
"Si vous savez que vous pouvez l'exécuter avec bash" Euh, non, parfois l'utilisateur n'a simplement aucune idée qu'il existe d'autres shells que ceux-là bash et que cela bash script.shpeut être dangereux.
Sergiy Kolodyazhnyy
10

Il existe plusieurs méthodes suggérées dans ce message Stack Overflow , et j’ai aimé la méthode basée sur les fonctions suggérée par Wirawan Purwanto et mr.spuratic best:

Le moyen le plus robuste, comme suggéré par Wirawan Purwanto, est de vérifier FUNCNAME[1] dans une fonction :

function mycheck() { declare -p FUNCNAME; }
mycheck

Ensuite:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

Cela équivaut à vérifier le résultat de caller, les valeurs mainet à sourcedistinguer le contexte de l'appelant. L'utilisation de FUNCNAME[]vous enregistre en capturant et en analysant la callersortie. Vous devez connaître ou calculer la profondeur de votre appel local pour être correct si. Des cas tels qu'un script provenant d'une autre fonction ou d'un autre script vont approfondir le tableau (pile). ( FUNCNAMEest une variable de tableau bash spéciale, elle devrait avoir des index contigus correspondant à la pile d'appels, tant que ce n'est jamais le cas unset.)

Vous pouvez donc ajouter au début du script:

function check()
{
    if [[ ${FUNCNAME[-1]} != "source" ]]   # bash 4.2+, use ${FUNCNAME[@]: -1} for older
    then
        printf "Usage: source %s\n" "$0"
        exit 1
    fi
}
check
muru
la source
7

En supposant que l’exécution du script soit inutile, plutôt que nuisible, vous pouvez ajouter

return 0 || printf 'Must be sourced, not executed\n' >&2

à la fin du script. returnEn dehors d'une fonction, le code de sortie est différent de zéro, sauf si le fichier est en cours d'acquisition.

chepner
la source
3
Notez que cela renvoie un état de sortie 0. Essayez ce langage similaire que j’utilise à la place:return 2>/dev/null; echo "$0: This script must be sourced" 1>&2; exit 1
wjandrea le
5

Lorsque vous sourcez un script shell, la ligne shebang est ignorée. En insérant un shebang non valide, vous pouvez avertir l'utilisateur que le script a été exécuté par erreur:

#!/bin/bash source-this-script
# ...

Le message d'erreur sera le suivant:

/bin/bash: source-this-script: No such file or directory

Le nom de l'argument (arbitraire) fournit déjà un indice fort, mais le message d'erreur n'est toujours pas clair à 100%. Nous pouvons résoudre ce problème avec un script utilitaire source-this-scriptplacé quelque part dans votre PATH:

#!/bin/sh
echo >&2 "This script must be sourced, not executed${1:+: }${1:-!}"
exit 1

Maintenant, le message d'erreur sera ceci:

This script must be sourced, not executed: path/to/script.sh

Comparaison avec d'autres approches

Comparée aux autres réponses, cette approche ne nécessite que des modifications minimes de chaque script (et avoir une ligne shebang facilite la détection du type de fichier dans les éditeurs et spécifie le dialecte du script shell, ce qui présente même des avantages). L'inconvénient est un message d'erreur quelque peu flou, ou l'ajout (ponctuel) d'un autre script shell.

Cela n'empêche cependant pas l'invocation explicite via bash path/to/script.sh(merci @muru!).

Ingo Karkat
la source
1
Un autre inconvénient est que cela ne protégera pas contre bash some/script.sh, ce qui ignorerait également le shebang.
Muru
4
Vous pouvez rendre le message plus clair en définissant le shebang #!/bin/echo 'You must source this script!'ou quelque chose du genre.
Chris
1
@Chris: D'accord, mais je perdrais alors la détection du type de fichier (par exemple dans Vim) et la documentation de quel dialecte shell il s'agit. Si vous ne vous en souciez pas, votre suggestion serait de supprimer le second script!
Ingo Karkat