Comment détecter si un script provient


217

J'ai un script où je ne veux pas qu'il appelle exits'il provient.

J'ai pensé à vérifier si $0 == bashcela avait des problèmes si le script provient d'un autre script, ou si l'utilisateur le source à partir d'un autre shell comme ksh.

Existe-t-il un moyen fiable de détecter si un script provient?


2
J'ai eu un problème similaire il y a quelque temps et je l'ai résolu en évitant la «sortie» dans tous les cas; "kill -INT $$" termine le script en toute sécurité dans les deux cas.
JESii

1
Avez-vous remarqué cette réponse ? Elle est donnée 5 ans plus tard à partir de l'acceptation, mais elle dispose de "piles incluses".
raratiru

Réponses:


73

Cela semble être portable entre Bash et Korn:

[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"

Une ligne similaire à celle-ci ou une affectation comme pathname="$_"(avec un test et une action ultérieurs) doit être sur la première ligne du script ou sur la ligne après le shebang (qui, si elle est utilisée, devrait être pour ksh pour qu'elle fonctionne sous la plupart des circonstances).


10
Malheureusement, il n'est pas garanti de fonctionner. Si l'utilisateur a défini BASH_ENV, $_en haut du script sera la dernière commande exécutée à partir de BASH_ENV.
Mikel

30
Cela ne fonctionnera pas non plus si vous utilisez bash pour exécuter le script, par exemple $ bash script.sh, alors $ _ serait / bin / bash au lieu de ./script.sh, ce qui est le cas auquel vous vous attendez lorsque le script est appelé de cette façon: $ ./script.sh Dans tous les cas, la détection avec $_est un problème.
Wirawan Purwanto

2
Des tests supplémentaires pourraient être inclus pour vérifier ces méthodes d'invocation.
pause jusqu'à nouvel ordre.

8
Malheureusement, c'est faux! voir ma réponse
F. Hauri

8
Pour résumer: Bien que cette approche fonctionne généralement , elle n'est pas robuste ; il échoue dans les 2 scénarios suivants: (a) bash script(invocation via un exécutable shell, que cette solution dénature comme source ), et (b) (beaucoup moins probable) echo bash; . script(si $_cela correspond au shell qui approvisionne le script, cette solution le dénature comme un sous - shell ). Seules les variables spéciales spécifiques au shell (par exemple, $BASH_SOURCE) permettent des solutions robustes (il s'ensuit qu'il n'y a pas de solution robuste compatible POSIX). Il est possible, quoique lourd, de concevoir un test croisé robuste.
mklement0

170

Si votre version Bash connaît la variable de tableau BASH_SOURCE, essayez quelque chose comme:

# man bash | less -p BASH_SOURCE
#[[ ${BASH_VERSINFO[0]} -le 2 ]] && echo 'No BASH_SOURCE array variable' && exit 1

[[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo "script ${BASH_SOURCE[0]} is being sourced ..."

11
C'est peut-être le moyen le plus propre, car $ BASH_SOURCE est exactement destiné à cet effet.
con-f-use

4
Notez que cela ne fonctionnera pas sous ksh qui est une condition spécifiée par l'OP.
pause jusqu'à nouvel ordre.

2
Y a-t-il une raison d'utiliser ${BASH_SOURCE[0]}au lieu de juste $BASH_SOURCE? Et ${0}vs $0?
hraban

4
BASH_SOURCEest une variable de tableau (voir manuel ) qui contient une trace de pile des sources, où ${BASH_SOURCE[0]}est la dernière. Les accolades sont utilisées ici pour indiquer au bash ce qui fait partie du nom de la variable. Ils ne sont pas nécessaires $0dans ce cas, mais ils ne font pas de mal non plus. ;)
Konrad

4
@Konrad, et si vous développez $array, vous obtenez ${array[0]}par défaut. Alors, encore une fois, y a-t-il une raison [...]?
Charles Duffy

133

Solutions solides pour bash, ksh,zsh , comprenant une contre-coquille une, plus une assez robuste solution compatible POSIX :

  • Les numéros de version sont données celles sur lesquelles la fonctionnalité a été vérifiée - probablement, ces solutions fonctionnent sur des versions beaucoup plus tôt, trop - bienvenue de retour .

  • En utilisant uniquement les fonctionnalités POSIX (comme dans dash, qui agit comme /bin/shsur Ubuntu), il n'existe aucun moyen robuste de déterminer si un script est en cours de sourcing - voir ci-dessous pour la meilleure approximation .

Les lignes simples suivent - explication ci-dessous; la version cross-shell est complexe, mais elle devrait fonctionner de manière robuste:

  • bash (vérifié les 3.57 et 4.4.19)

    (return 0 2>/dev/null) && sourced=1 || sourced=0
  • ksh (vérifié sur 93u +)

    [[ $(cd "$(dirname -- "$0")" && 
       printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
         sourced=1 || sourced=0
  • zsh (vérifié sur 5.0.5) - assurez-vous d'appeler cela en dehors d'une fonction

    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
  • cross-shell (bash, ksh, zsh)

    ([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] || 
     [[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
        printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] || 
     [[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
  • Conforme POSIX ; pas un seul liner (pipeline unique) pour des raisons techniques et pas entièrement robuste (voir en bas):

    sourced=0
    if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
      case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
    elif [ -n "$KSH_VERSION" ]; then
      [ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
    elif [ -n "$BASH_VERSION" ]; then
      (return 0 2>/dev/null) && sourced=1 
    else # All other shells: examine $0 for known shell binary filenames
      # Detects `sh` and `dash`; add additional shell filenames as needed.
      case ${0##*/} in sh|dash) sourced=1;; esac
    fi

Explication:


frapper

(return 0 2>/dev/null) && sourced=1 || sourced=0

Remarque: La technique a été adaptée de la réponse de user5754163 , car elle s'est avérée être plus robuste que la solution d'origine, [[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0[1]

  • Bash autorise les returninstructions uniquement à partir des fonctions et, dans la portée de niveau supérieur d'un script, uniquement si le script provient .

    • Si returnest utilisé dans la portée de niveau supérieur d'un script non source , un message d'erreur est émis et le code de sortie est défini sur 1.
  • (return 0 2>/dev/null)s'exécute returndans un sous - shell et supprime le message d'erreur; ensuite, le code de sortie indique si le script a été généré ( 0) ou non ( 1), qui est utilisé avec les opérateurs &&et ||pour définir la sourcedvariable en conséquence.

    • L'utilisation d'un sous-shell est nécessaire, car l'exécution returndans la portée de niveau supérieur d'un script source quitterait le script.
    • Astuce du chapeau à @Haozhun , qui a rendu la commande plus robuste en utilisant explicitement 0comme returnopérande; il note: par bash aide de return [N]: "Si N est omis, le statut de retour est celui de la dernière commande." Par conséquent, la version antérieure [qui utilisait juste return, sans opérande] produit un résultat incorrect si la dernière commande sur le shell de l'utilisateur a une valeur de retour non nulle.

ksh

[[ \
   $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != \
   "${.sh.file}" \
]] && 
sourced=1 || sourced=0

La variable spéciale ${.sh.file}est quelque peu analogue à $BASH_SOURCE; notez que cela ${.sh.file}provoque une erreur de syntaxe dans bash, zsh et dash, alors assurez-vous de l'exécuter conditionnellement dans des scripts multi-shell.

Contrairement à bash, $0et ne ${.sh.file}sont PAS garantis pour être exactement identiques dans le cas non-source, comme $0peut être un chemin relatif , alors qu'il ${.sh.file}s'agit toujours d'un chemin complet , il $0faut donc le résoudre en chemin complet avant de comparer.


zsh

[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0

$ZSH_EVAL_CONTEXTcontient des informations sur le contexte d'évaluation - appelez cela en dehors d'une fonction. À l'intérieur d'un script d'origine [portée de niveau supérieur de], $ZSH_EVAL_CONTEXT se termine par :file.

Caveat: A l' intérieur d' une substitution de commande, zsh ajouter ses :cmdsubst, donc essai $ZSH_EVAL_CONTEXTpour :file:cmdsubst$là - bas.


Utilisation des fonctionnalités POSIX uniquement

Si vous êtes prêt à faire certaines hypothèses, vous pouvez faire une estimation raisonnable, mais non infaillible, de savoir si votre script est d'origine, sur la base de la connaissance des noms de fichiers binaires des shells qui peuvent exécuter votre script .
Cela signifie notamment que cette approche échoue si votre script provient d' un autre script .

La section "Comment gérer les appels provenant de sources" dans ma réponse traite des cas marginaux qui ne peuvent pas être traités avec les fonctionnalités POSIX uniquement en détail.

Cela repose sur le comportement standard de $0, qui zsh, par exemple, ne présente pas .

Ainsi, l'approche la plus sûre consiste à combiner les méthodes robustes spécifiques au shell ci-dessus avec une solution de secours pour tous les shells restants.

Pointe du chapeau à Stéphane Desneux et sa réponse pour l'inspiration (transformer mon expression de déclaration cross-shell en une déclaration shcompatible ifet ajouter un gestionnaire pour d'autres coquilles).

sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
  [ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
  (return 0 2>/dev/null) && sourced=1 
else # All other shells: examine $0 for known shell binary filenames
  # Detects `sh` and `dash`; add additional shell filenames as needed.
  case ${0##*/} in sh|dash) sourced=1;; esac
fi

[1] user1902689 a découvert que renvoie[[ $0 != "$BASH_SOURCE" ]] un faux positif lorsque vous exécutez un script situé dans le$PATH en passant son simple nom de fichier au bashbinaire; par exemple,, bash my-scriptcar $0est alors juste my-script, alors que $BASH_SOURCEc'est le chemin complet . Bien que vous n'utilisiez normalement pas cette technique pour appeler des scripts dans $PATH- vous les invoqueriez directement ( my-script) - elle est utile lorsqu'elle est combinée avec -xpour le débogage .


1
bravo pour une réponse aussi complète.
DrUseful

75

Après avoir lu la réponse de @ DennisWilliamson, il y a quelques problèmes, voir ci-dessous:

Cette question étant et , il y a une autre partie de cette réponse concernant ... voir ci-dessous.

Facile façon

[ "$0" = "$BASH_SOURCE" ]

Essayons (à la volée car ce bash pourrait ;-):

source <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

bash <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 16229 is own (/dev/fd/63, /dev/fd/63)

J'utilise sourceplutôt off .pour plus de lisibilité (comme .c'est un alias pour source):

. <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

Notez que le numéro de processus ne change pas tant que le processus reste source :

echo $$
29301

Pourquoi ne pas utiliser la $_ == $0comparaison

Pour assurer de nombreux cas, je commence à écrire un vrai script:

#!/bin/bash

# As $_ could be used only once, uncomment one of two following lines

#printf '_="%s", 0="%s" and BASH_SOURCE="%s"\n' "$_" "$0" "$BASH_SOURCE"
[[ "$_" != "$0" ]] && DW_PURPOSE=sourced || DW_PURPOSE=subshell

[ "$0" = "$BASH_SOURCE" ] && BASH_KIND_ENV=own || BASH_KIND_ENV=sourced;
echo "proc: $$[ppid:$PPID] is $BASH_KIND_ENV (DW purpose: $DW_PURPOSE)"

Copiez ceci dans un fichier appelé testscript:

cat >testscript   
chmod +x testscript

Maintenant, nous pourrions tester:

./testscript 
proc: 25758[ppid:24890] is own (DW purpose: subshell)

C'est bon.

. ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

source ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

C'est bon.

Mais, pour tester un script avant d'ajouter un -xindicateur:

bash ./testscript 
proc: 25776[ppid:24890] is own (DW purpose: sourced)

Ou pour utiliser des variables prédéfinies:

env PATH=/tmp/bintemp:$PATH ./testscript 
proc: 25948[ppid:24890] is own (DW purpose: sourced)

env SOMETHING=PREDEFINED ./testscript 
proc: 25972[ppid:24890] is own (DW purpose: sourced)

Cela ne fonctionnera plus.

Déplacer le commentaire de la 5e ligne à la 6e donnerait une réponse plus lisible:

./testscript 
_="./testscript", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26256[ppid:24890] is own

. testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

source testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

bash testscript 
_="/bin/bash", 0="testscript" and BASH_SOURCE="testscript"
proc: 26317[ppid:24890] is own

env FILE=/dev/null ./testscript 
_="/usr/bin/env", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26336[ppid:24890] is own

Plus fort: maintenant...

Comme je n'utilise pas beaucoup, après quelques lectures sur la page de manuel, voici mes essais:

#!/bin/ksh

set >/tmp/ksh-$$.log

Copiez ceci dans un testfile.ksh:

cat >testfile.ksh
chmod +x testfile.ksh

Ensuite, exécutez-le deux fois:

./testfile.ksh
. ./testfile.ksh

ls -l /tmp/ksh-*.log
-rw-r--r-- 1 user user   2183 avr 11 13:48 /tmp/ksh-9725.log
-rw-r--r-- 1 user user   2140 avr 11 13:48 /tmp/ksh-9781.log

echo $$
9725

et voyez:

diff /tmp/ksh-{9725,9781}.log | grep ^\> # OWN SUBSHELL:
> HISTCMD=0
> PPID=9725
> RANDOM=1626
> SECONDS=0.001
>   lineno=0
> SHLVL=3

diff /tmp/ksh-{9725,9781}.log | grep ^\< # SOURCED:
< COLUMNS=152
< HISTCMD=117
< LINES=47
< PPID=9163
< PS1='$ '
< RANDOM=29667
< SECONDS=23.652
<   level=1
<   lineno=1
< SHLVL=2

Il y a une variable héritée dans une analyse d'origine , mais rien de vraiment lié ...

Vous pouvez même vérifier que $SECONDSc'est proche 0.000, mais cela garantit que seuls les cas d' origine manuelle ...

Vous pouvez même essayer de vérifier ce qu'est le parent:

Placez-le dans votre testfile.ksh:

ps $PPID

Que:

./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32320 pts/4    Ss     0:00 -ksh

. ./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32319 ?        S      0:00 sshd: user@pts/4

ou ps ho cmd $PPID, mais cela ne fonctionne que pour un seul niveau de sous-sessions ...

Désolé, je n'ai pas pu trouver un moyen fiable de le faire, sous .


[ "$0" = "$BASH_SOURCE" ] || [ -z "$BASH_SOURCE" ]pour les scripts lus via pipe ( cat script | bash).
hakre

2
Notez que ce .n'est pas un alias pour source, c'est en fait l'inverse. source somescript.shest un Bash-ism et n'est pas portable, . somescript.shest POSIX et IIRC portable.
dragon788

32

La BASH_SOURCE[]réponse (bash-3.0 et versions ultérieures) semble la plus simple, bien qu'elle neBASH_SOURCE[] soit pas documentée pour fonctionner en dehors d'un corps de fonction (elle fonctionne actuellement, en désaccord avec la page de manuel).

Le moyen le plus robuste, comme suggéré par Wirawan Purwanto, est de vérifier FUNCNAME[1] au sein d'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 revient à vérifier la sortie de caller, les valeurs mainet à sourcedistinguer le contexte de l'appelant. L'utilisation FUNCNAME[]vous évite de capturer et d'analyser la callersortie. Vous devez cependant connaître ou calculer la profondeur de votre appel local pour être correct. Des cas comme un script provenant d'une autre fonction ou d'un autre script entraîneront un approfondissement du tableau (pile). ( FUNCNAMEest une variable de tableau bash spéciale, elle doit avoir des index contigus correspondant à la pile d'appels, tant qu'elle ne l'est jamais unset.)

function issourced() {
    [[ ${FUNCNAME[@]: -1} == "source" ]]
}

(Dans bash-4.2 et versions ultérieures, vous pouvez utiliser le formulaire plus simple à la ${FUNCNAME[-1]}place pour le dernier élément du tableau. Amélioré et simplifié grâce au commentaire de Dennis Williamson ci-dessous.)

Cependant, votre problème, comme indiqué, est " J'ai un script où je ne veux pas qu'il appelle" exit "s'il est d'origine ". L' bashidiome commun à cette situation est:

return 2>/dev/null || exit

Si le script est recherché, il returnmettra fin au script source et reviendra à l'appelant.

Si le script est en cours d'exécution, alors returnretournera une erreur (redirigée) et exitterminera le script normalement. Les deux returnet exitpeuvent prendre un code de sortie, si nécessaire.

Malheureusement, cela ne fonctionne pas ksh(du moins pas dans la version dérivée d'AT & T que j'ai ici), elle est considérée returncomme équivalente à exitsi elle est invoquée en dehors d'une fonction ou d'un script source.

Mise à jour : ce que vous pouvez faire dans les versions contemporaines de, kshc'est vérifier la variable spéciale .sh.levelqui est définie sur la profondeur d'appel de la fonction. Pour un script appelé, ce paramètre sera initialement non défini, pour un script source de points, il sera défini sur 1.

function issourced {
    [[ ${.sh.level} -eq 2 ]]
}

issourced && echo this script is sourced

Ce n'est pas aussi robuste que la version bash, vous devez invoquer issourced()dans le fichier que vous testez au niveau supérieur ou à une profondeur de fonction connue.

(Vous pouvez également être intéressé par ce code sur github qui utilise une kshfonction de discipline et une astuce de piège de débogage pour émuler le FUNCNAMEtableau bash .)

La réponse canonique ici: http://mywiki.wooledge.org/BashFAQ/109 offre également un $-autre indicateur (bien qu'imparfait) de l'état du shell.


Remarques:

  • il est possible de créer des fonctions bash nommées "main" et "source" (en remplaçant la fonction intégrée ), ces noms peuvent apparaître dans FUNCNAME[]mais tant que seul le dernier élément de ce tableau est testé, il n'y a aucune ambiguïté.
  • Je n'ai pas de bonne réponse pour pdksh. La chose la plus proche que je peux trouver ne s'applique qu'à pdksh, où chaque sourcing d'un script ouvre un nouveau descripteur de fichier (commençant par 10 pour le script d'origine). Ce n'est certainement pas quelque chose sur lequel vous voulez compter ...

Que diriez-vous ${FUNCNAME[(( ${#FUNCNAME[@]} - 1 ))]}d'obtenir le dernier élément (en bas) de la pile? Ensuite, les tests par rapport à "main" (négation pour OP) ont été les plus fiables pour moi.
Adrian Günter

Si j'ai un PROMPT_COMMANDensemble, cela apparaît comme le dernier index du FUNCNAMEtableau si je cours source sourcetest.sh. Le chèque inverser (recherche maincomme le dernier indice) semble plus robuste: is_main() { [[ ${FUNCNAME[@]: -1} == "main" ]]; }.
dimo414

1
La page de manuel indique que cela FUNCNAMEn'est disponible que dans les fonctions. Selon mes tests avec declare -p FUNCNAME, bashse comporte différemment. v4.3 donne une erreur en dehors des fonctions, alors que v4.4 donne declare -a FUNCNAME. Les deux (!) Retour mainpour ${FUNCNAME[0]}dans le script principal (si elle est exécutée) tout en $FUNCNAMEne donne rien. Et: Il y a tellement de scripts "ab" qui utilisent $BASH_SOURCEdes fonctions externes, que je doute que cela puisse ou sera changé.
Tino

24

Note de l'éditeur: la solution de cette réponse fonctionne de manière robuste, mais ne l'est que bash. Il peut être simplifié
(return 2>/dev/null).

TL; DR

Essayez d'exécuter une returninstruction. Si le script n'est pas d'origine, cela générera une erreur. Vous pouvez intercepter cette erreur et continuer selon vos besoins.

Mettez ceci dans un fichier et appelez-le, disons, test.sh:

#!/usr/bin/env sh

# Try to execute a `return` statement,
# but do it in a sub-shell and catch the results.
# If this script isn't sourced, that will raise an error.
$(return >/dev/null 2>&1)

# What exit code did that give?
if [ "$?" -eq "0" ]
then
    echo "This script is sourced."
else
    echo "This script is not sourced."
fi

Exécutez-le directement:

shell-prompt> sh test.sh
output: This script is not sourced.

Source:

shell-prompt> source test.sh
output: This script is sourced.

Pour moi, cela fonctionne en zsh et bash.

Explication

L' returninstruction génère une erreur si vous essayez de l'exécuter en dehors d'une fonction ou si le script n'est pas d'origine. Essayez ceci à partir d'une invite shell:

shell-prompt> return
output: ...can only `return` from a function or sourced script

Vous n'avez pas besoin de voir ce message d'erreur, vous pouvez donc rediriger la sortie vers dev / null:

shell-prompt> return >/dev/null 2>&1

Vérifiez maintenant le code de sortie. 0 signifie OK (aucune erreur n'est survenue), 1 signifie qu'une erreur s'est produite:

shell-prompt> echo $?
output: 1

Vous souhaitez également exécuter l' returninstruction à l'intérieur d'un sous-shell. Lorsque l' returninstruction l'exécute. . . bien . . . Retour. Si vous l'exécutez dans un sous-shell, il sortira de ce sous-shell, plutôt que de sortir de votre script. Pour exécuter dans le sous-shell, enveloppez-le dans $(...):

shell-prompt> $(return >/dev/null 2>$1)

Maintenant, vous pouvez voir le code de sortie du sous-shell, qui devrait être 1, car une erreur a été déclenchée à l'intérieur du sous-shell:

shell-prompt> echo $?
output: 1

Cela échoue pour moi en 0.5.8-2.1ubuntu2 $ readlink $(which sh) dash $ . test.sh This script is sourced. $ ./test.sh This script is sourced.
Phil Rutschman

3
POSIX ne spécifie pas ce qui returndoit être fait au niveau supérieur ( pubs.opengroup.org/onlinepubs/9699919799/utilities/… ). La dashcoque traite un returnau niveau supérieur comme exit. D'autres coquilles aiment bashou zshn'autorisent pas returnau niveau supérieur, ce qui est la caractéristique qu'une technique comme celle-ci exploite.
user5754163

Cela fonctionne en sh si vous supprimez l' $avant du sous-shell. Autrement dit, utilisez (return >/dev/null 2>&1)au lieu de $(return >/dev/null 2>&1)- mais cela cesse de fonctionner dans bash.
Éponyme du

@Eponymous: Puisque dash, lorsque cette solution ne fonctionne pas, agit comme shsur Ubuntu, par exemple, cette solution ne fonctionne généralement pas avec sh. La solution fonctionne pour moi dans Bash 3.2.57 et 4.4.5 - avec ou sans le $précédent (...)(bien qu'il n'y ait jamais de bonne raison pour le $).
mklement0

2
returnsans aucune valeur de retour explicite ne se casse lors de l’ingénierie sourcedes scripts juste après une commande mal exécutée. Proposition de modification de l'amélioration.
DimG

12

FWIW, après avoir lu toutes les autres réponses, j'ai trouvé la solution suivante pour moi:

Mise à jour: En fait, quelqu'un a repéré une erreur corrigée depuis dans une autre réponse qui a également affecté la mienne. Je pense que la mise à jour ici est également une amélioration (voir les modifications si vous êtes curieux).

Cela fonctionne pour tous les scripts, qui commencent par#!/bin/bash mais qui peuvent également provenir de différents shells pour apprendre certaines informations (comme les paramètres) qui sont conservées en dehors de la mainfonction.

Selon les commentaires ci-dessous, cette réponse ici ne fonctionne apparemment pas pour toutes les bashvariantes. Pas non plus pour les systèmes, où /bin/shest basé bash. IE, il échoue pour bashv3.x sur MacOS. (Actuellement, je ne sais pas comment résoudre ce problème.)

#!/bin/bash

# Function definitions (API) and shell variables (constants) go here
# (This is what might be interesting for other shells, too.)

# this main() function is only meant to be meaningful for bash
main()
{
# The script's execution part goes here
}

BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "$@"

Au lieu des 2 dernières lignes, vous pouvez utiliser le code suivant (à mon avis moins lisible) pour ne pas définir BASH_SOURCEdans d'autres shells et permettre set -ede travailler dans main:

if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "$@"; fi

Cette recette de script a les propriétés suivantes:

  • S'il est exécuté par bashla voie normale, mainest appelé. Veuillez noter que cela n'inclut pas un appel comme bash -x script(où scriptne contient pas de chemin), voir ci-dessous.

  • S'il provient de bash, mainn'est appelé que si le script appelant se trouve avoir le même nom. (Par exemple, s'il s'approvisionne lui-même ou via bash -c 'someotherscript "$@"' main-script args..main-scriptdoit être, ce qui testapparaît comme $BASH_SOURCE).

  • S'il provient / est exécuté / lu / evalédité par un shell autre que bash, mainn'est pas appelé ( BASH_SOURCEest toujours différent de $0).

  • mainn'est pas appelé si bashlit le script depuis stdin, sauf si vous définissez $0la chaîne vide comme ceci:( exec -a '' /bin/bash ) <script

  • Si évalué par bashavec eval ( eval "`cat script`" toutes les citations sont importantes! ) À partir d'un autre script, cela appelle main. Si evalest exécuté directement à partir de la ligne de commande, cela est similaire au cas précédent, où le script est lu depuis stdin. ( BASH_SOURCEest vide, alors qu'il $0est généralement /bin/bashsinon forcé à quelque chose de complètement différent.)

  • Si mainn'est pas appelé, il retourne true( $?=0).

  • Cela ne repose pas sur un comportement inattendu (auparavant, j'écrivais non documenté, mais je n'ai trouvé aucune documentation que vous ne pouvez unsetni modifier BASH_SOURCEnon plus):

    • BASH_SOURCEest un tableau réservé à bash . Mais permettre BASH_SOURCE=".$0"de le changer ouvrirait une boîte de vers très dangereuse, donc je m'attends à ce que cela ne produise aucun effet (sauf, peut-être, un avertissement laid apparaîtra dans une future version de bash).
    • Il n'y a aucune documentation qui BASH_SOURCEfonctionne en dehors des fonctions. Cependant le contraire (qu'il ne fonctionne que dans les fonctions) n'est ni documenté. L'observation est que cela fonctionne (testé avec bashv4.3 et v4.4, malheureusement je n'ai plus de bashv3.x) et que trop de scripts se briseraient, s'ils $BASH_SOURCEcessent de fonctionner comme observé. Par conséquent, je m'attends à ce que cela BASH_SOURCEreste aussi pour les futures versions de bash.
    • En revanche (belle trouvaille, BTW!) Considérez ( return 0 )ce qui donne 0si sourcé et 1si non sourcé. Cela vient un peu inattendu non seulement pour moi , et (selon les lectures là-bas) POSIX dit que le returnsous-shell est un comportement indéfini (et le returnici vient clairement d'un sous-shell). Peut-être que cette fonctionnalité finira par être suffisamment utilisée pour ne plus pouvoir être modifiée, mais AFAICS il y a beaucoup plus de chances que certaines bashversions futures modifient accidentellement le comportement de retour dans ce cas.
  • Malheureusement, bash -x script 1 2 3ne fonctionne pas main. (Comparez script 1 2 3scriptn'a pas de chemin). La solution suivante peut être utilisée comme solution de contournement:

    • bash -x "`which script`" 1 2 3
    • bash -xc '. script' "`which script`" 1 2 3
    • Cela bash script 1 2 3ne fonctionne pas mainpeut être considéré comme une fonctionnalité.
  • Notez que les ( exec -a none script )appels main( bashne sont pas transmis $0au script, pour cela, vous devez les utiliser -ccomme indiqué au dernier point).

Ainsi, à l'exception de certains cas de coin, mainn'est appelé que lorsque le script est exécuté de la manière habituelle. Normalement, c'est ce que vous voulez, surtout parce qu'il manque un code complexe et difficile à comprendre.

Notez qu'il est très similaire au code Python:

if __name__ == '__main__': main()

Ce qui empêche également d'appeler main, sauf pour certains cas d'angle, car vous pouvez importer / charger le script et l'imposer__name__='__main__'

Pourquoi je pense que c'est une bonne façon générale de résoudre le défi

Si vous avez quelque chose, qui peut provenir de plusieurs shells, il doit être compatible. Cependant (lisez les autres réponses), car il n'y a pas de moyen portable (facile à mettre en œuvre) pour détecter l' sourceing, vous devez changer les règles .

En imposant que le script doit être exécuté par /bin/bash, vous faites exactement cela.

Cela résout tous les cas, mais les suivants, auquel cas le script ne peut pas s'exécuter directement:

  • /bin/bash n'est pas installé ou ne fonctionne pas (i. E. dans un environnement de démarrage)
  • Si vous le dirigez vers un shell comme dans curl https://example.com/script | $SHELL
  • (Remarque: cela n'est vrai que si votre version bashest suffisamment récente. Cette recette aurait échoué pour certaines variantes. Assurez-vous donc de vérifier qu'elle fonctionne pour votre cas.)

Cependant, je ne peux penser à aucune raison réelle pour laquelle vous avez besoin de cela et également à la possibilité de source exactement le même script en parallèle! Habituellement, vous pouvez l'envelopper pour exécuter mainmanuellement. Comme ça:

  • $SHELL -c '. script && main'
  • { curl https://example.com/script && echo && echo main; } | $SHELL
  • $SHELL -c 'eval "`curl https://example.com/script`" && main'
  • echo 'eval "`curl https://example.com/script`" && main' | $SHELL

Remarques

  • Cette réponse n'aurait pas été possible sans l'aide de toutes les autres réponses! Même les mauvais - ce qui m'a d'abord fait publier ceci.

  • Mise à jour: modifiée en raison des nouvelles découvertes trouvées dans https://stackoverflow.com/a/28776166/490291


Testé pour ksh et bash-4.3. Agréable. C'est vraiment dommage que votre réponse ait une vie difficile étant donné que les autres réponses ont déjà eu des années à collecter des votes positifs.
hagello

Merci pour cette réponse. J'ai apprécié le test plus long et «moins lisible» avec l'instruction IF, car il est agréable de gérer les deux situations pour au moins donner un échec non silencieux. Dans mon cas, j'ai besoin qu'un script soit fourni ou informe autrement l'utilisateur de son erreur de ne pas utiliser la source.
Tim Richardson

@Tino: Quant à "pourrait également provenir de différents shells": Sur macOS, où /bin/shest effectivement bashen mode POSIX, l'affectation à BASH_SOURCE casse votre script. Dans d' autres coquilles ( dash, ksh, zsh), invoquant votre script en passant comme un argument de fichier directement aux exécutables shell dysfonctionnements (par exemple, zsh <your-script>fera votre script pensent à tort qu'il est sourced ). (Vous mentionnez déjà que le piping du code fonctionne mal, dans tous les shells.)
mklement0

@Tino: En passant: bien que . <your-script>(l'approvisionnement) fonctionne en principe à partir de tous les shells POSIX, cela n'a de sens que si le script a été explicitement écrit pour utiliser uniquement les fonctionnalités POSIX, afin d'empêcher les fonctionnalités spécifiques à un shell de interrompre l'exécution dans d'autres coquilles; utiliser une ligne de Bash Shebang (plutôt que #!/bin/sh) est donc déroutant - au moins sans commentaire évident. Inversement, si votre script est destiné à être exécuté uniquement à partir de Bash (même si ce n'est qu'en ne tenant pas compte des fonctionnalités qui peuvent ne pas être portables), il est préférable de refuser l' exécution dans des shells autres que Bash.
mklement0

1
@ mklement0 Merci encore, a ajouté une note qu'il y a un problème. Pour les autres lecteurs: lorsqu'il provient de bash v3.x, il ne devrait pas s'exécuter main, mais il le fait dans ce cas! Et lorsqu'il provient de /bin/shce qui est bash --posix, la même chose se produit dans ce cas, et c'est tout aussi faux.
Tino

6

Cela fonctionne plus tard dans le script et ne dépend pas de la variable _:

## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename $0) = $Prog ]; then
   exit 1  # not sourced
fi

ou

[ $(basename $0) = $Prog ] && exit

1
Je pense que cette réponse est l'une des rares conformes POSIX ici. Avec les inconvénients évidents, vous devez connaître le nom de fichier et cela ne fonctionne pas si les deux scripts ont le même nom de fichier.
JepZ

5

Je vais donner une réponse spécifique à BASH. Korn shell, désolé. Supposons que le nom de votre script soit include2.sh; puis faites une fonction à l' intérieur de l' include2.shappelé am_I_sourced. Voici ma version de démonstration de include2.sh:

am_I_sourced()
{
  if [ "${FUNCNAME[1]}" = source ]; then
    if [ "$1" = -v ]; then
      echo "I am being sourced, this filename is ${BASH_SOURCE[0]} and my caller script/shell name was $0"
    fi
    return 0
  else
    if [ "$1" = -v ]; then
      echo "I am not being sourced, my script/shell name was $0"
    fi
    return 1
  fi
}

if am_I_sourced -v; then
  echo "Do something with sourced script"
else
  echo "Do something with executed script"
fi

Essayez maintenant de l'exécuter de plusieurs façons:

~/toys/bash $ chmod a+x include2.sh

~/toys/bash $ ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ bash ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ . include2.sh
I am being sourced, this filename is include2.sh and my caller script/shell name was bash
Do something with sourced script

Cela fonctionne donc sans exception, et il n'utilise pas les $_choses fragiles . Cette astuce utilise la fonction d'introspection de BASH, c'est-à-dire les variables intégrées FUNCNAMEet BASH_SOURCE; voir leur documentation dans la page de manuel bash.

Seulement deux mises en garde:

1) l'appel à am_I_called doit avoir lieu dans le script d'origine, mais pas dans aucune fonction, de peur de ${FUNCNAME[1]}renvoyer autre chose. Ouais ... tu aurais pu vérifier ${FUNCNAME[2]}- mais tu te rends la vie plus difficile.

2) la fonction am_I_called doit résider dans le script d'origine si vous voulez savoir quel est le nom du fichier inclus.


1
Clarification: Cette fonctionnalité nécessite BASH version 3+ pour fonctionner. Dans BASH 2, FUNCNAME est une variable scalaire au lieu d'un tableau. BASH 2 n'a pas non plus de variable de tableau BASH_SOURCE.
Wirawan Purwanto

4

Je voudrais suggérer une petite correction à la réponse très utile de Dennis , pour la rendre légèrement plus portable, j'espère:

[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"

parce que [[n'est pas reconnu par le ( un peu à mon humble avis anal rémanentes) Debian compatible POSIX shell, dash. De plus, on peut avoir besoin des guillemets pour se protéger contre les noms de fichiers contenant des espaces, là encore dans ledit shell.


2

$_est assez fragile. Vous devez le vérifier comme première chose que vous faites dans le script. Et même dans ce cas, il n'est pas garanti de contenir le nom de votre shell (s'il provient) ou le nom du script (s'il est exécuté).

Par exemple, si l'utilisateur a défini BASH_ENV, alors en haut d'un script, $_contient le nom de la dernière commande exécutée dans le BASH_ENVscript.

La meilleure façon que j'ai trouvée est d'utiliser $0comme ceci:

name="myscript.sh"

main()
{
    echo "Script was executed, running main..."
}

case "$0" in *$name)
    main "$@"
    ;;
esac

Malheureusement, cette méthode ne fonctionne pas dès le départ dans zsh car l' functionargzerooption fait plus que son nom l'indique et est activée par défaut.

Pour contourner cela, j'ai mis unsetopt functionargzeromon .zshenv.


1

J'ai suivi l' expression compacte mklement0 .

C'est bien, mais j'ai remarqué qu'il peut échouer dans le cas de ksh lorsqu'il est appelé comme ceci:

/bin/ksh -c ./myscript.sh

(il pense qu'il est originaire et ce n'est pas parce qu'il exécute un sous-shell) Mais l'expression fonctionnera pour détecter cela:

/bin/ksh ./myscript.sh

De plus, même si l'expression est compacte, la syntaxe n'est pas compatible avec tous les shells.

J'ai donc terminé avec le code suivant, qui fonctionne pour bash, zsh, dash et ksh

SOURCED=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && SOURCED=1
elif [ -n "$KSH_VERSION" ]; then
    [[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ]] && SOURCED=1
elif [ -n "$BASH_VERSION" ]; then
    [[ $0 != "$BASH_SOURCE" ]] && SOURCED=1
elif grep -q dash /proc/$$/cmdline; then
    case $0 in *dash*) SOURCED=1 ;; esac
fi

N'hésitez pas à ajouter le support des coquilles exotiques :)


Dans ksh 93+u, ksh ./myscript.shfonctionne bien pour moi (avec ma déclaration) - quelle version utilisez-vous?
mklement0

Je crains qu'il n'y ait aucun moyen de déterminer de manière fiable si un script provient uniquement des fonctionnalités POSIX: votre tentative suppose Linux ( /proc/$$/cmdline) et se concentre dashuniquement sur (qui agit également comme shsur Ubuntu, par exemple). Si vous êtes prêt à faire certaines hypothèses, vous pouvez $0rechercher un test raisonnable - mais incomplet - portable.
mklement0

++ pour l'approche de base, cependant - j'ai pris la liberté de l'adapter à ce que je pense être la meilleure approximation portable de support sh/ dashainsi, dans un addendum à ma réponse.
mklement0

0

Je ne pense pas qu'il existe un moyen portable de le faire à la fois dans ksh et bash. En bash, vous pouvez le détecter en utilisant la callersortie, mais je ne pense pas qu'il existe un équivalent dans ksh.


$0travaille dans bash, ksh93et pdksh. Je n'ai pas ksh88à tester.
Mikel

0

J'avais besoin d'un one-liner qui fonctionne sur [mac, linux] avec bash.version> = 3 et aucune de ces réponses ne correspond à la facture.

[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"

1
La bashsolution fonctionne bien (vous pourriez simplifier $BASH_SOURCE), mais la kshsolution n'est pas robuste: si votre script provient d' un autre script , vous obtiendrez un faux positif.
mklement0

0

Droit au but: vous devez évaluer si la variable "$ 0" est égale au nom de votre Shell.


Comme ça:

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi


Via SHELL :

$ bash check_source.sh 
First Parameter: check_source.sh

The script WAS NOT sourced.

Via SOURCE :

$ source check_source.sh
First Parameter: bash

The script was sourced.



Il est assez difficile d'avoir un moyen 100% portable de détecter si un script a été sourcé ou non.

En ce qui concerne mon expérience (7 ans avec Shellscripting) , le seul moyen sûr (ne pas s'appuyer sur des variables d'environnement avec des PID et ainsi de suite, qui n'est pas sûr car c'est quelque chose de VARIABLE ), vous devez:

  • étendre les possibilités de votre si
  • en utilisant un commutateur / boîtier, si vous le souhaitez.

Les deux options ne peuvent pas être mises à l'échelle automatiquement, mais c'est la manière la plus sûre.



Par exemple:

lorsque vous sourcez un script via une session SSH, la valeur renvoyée par la variable "$ 0" (lors de l'utilisation de source ) est -bash .

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" || "$0" == "-bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi

OU

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
elif [[ "$0" == "-bash" ]] ; then
    echo "The script was sourced via SSH session."
else
    echo "The script WAS NOT sourced."
fi

2
Voté, car c'est tout à fait faux: /bin/bash -c '. ./check_source.sh'donne The script WAS NOT sourced.. Même bug: ln -s /bin/bash pumuckl; ./pumuckl -c '. ./check_source.sh'->The script WAS NOT sourced.
Tino

2
Votre vote négatif a changé tout le scénario et a apporté une grande contribution, Tino. Merci!
ivanleoncz

0

J'ai fini par vérifier [[ $_ == "$(type -p "$0")" ]]

if [[ $_ == "$(type -p "$0")" ]]; then
    echo I am invoked from a sub shell
else
    echo I am invoked from a source command
fi

Lorsqu'il est utilisé curl ... | bash -s -- ARGSpour exécuter un script distant à la volée, le $ 0 sera juste bashau lieu de normal /bin/bashlors de l'exécution du fichier de script réel, donc j'utilise type -p "$0"pour afficher le chemin complet de bash.

tester:

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)
relpath /a/b/c/d/e /a/b/CC/DD/EE

wget https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath
chmod +x relpath
./relpath /a/b/c/d/e /a/b/CC/DD/EE

0

Ceci est un spin off de quelques autres réponses, concernant le support "universel" de shell croisé. Certes, cela est très similaire à https://stackoverflow.com/a/2942183/3220983 en particulier, bien que légèrement différent. La faiblesse de ceci, c'est qu'un script client doit respecter la façon de l'utiliser (c'est-à-dire en exportant d'abord une variable). La force est que cela est simple et devrait fonctionner "n'importe où". Voici un modèle pour votre plaisir de copier-coller:

# NOTE: This script may be used as a standalone executable, or callable library.
# To source this script, add the following *prior* to including it:
# export ENTRY_POINT="$0"

main()
{
    echo "Running in direct executable context!"
}

if [ -z "${ENTRY_POINT}" ]; then main "$@"; fi

Remarque: J'utilise exportjuste être sûr que ce mécanisme peut être étendu en sous-processus.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.