web-dev-qa-db-fra.com

Comment détecter si un script est recherché

J'ai un script dans lequel je ne veux pas qu'il appelle exit s'il provient. 

J'ai pensé à vérifier si $0 == bash, mais cela pose 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 est recherché?

154
brianegge

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 telle que pathname="$_" (avec un test et une action ultérieurs) doit figurer sur la première ligne du script ou sur la ligne après le Shebang (qui, si elle est utilisée, doit être pour ksh afin que cela fonctionne dans la plupart des circonstances).

51
Dennis Williamson

Si votre version de Bash est au courant de 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 ..."
133
barroyo

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

Comme cette question correspond à kshetbash , il existe un autre élément dans cette réponse concernant ksh ... voir ci-dessous.

Simple bash way

[ "$0" = "$BASH_SOURCE" ]

Essayons (sur le vif car cette 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 source à la place de . pour la lisibilité (car . est un alias de 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 comparaison $_ == $0

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

#!/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 nommé 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 -x flag:

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

Ou 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 5ème ligne à la 6ème 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

Harder: ksh maintenant ...

Comme je n’utilise pas souvent ksh , après quelques lectures sur la page de manuel, il ya mes essais:

#!/bin/ksh

set >/tmp/ksh-$$.log

Copiez ceci dans un testfile.ksh:

cat >testfile.ksh
chmod +x testfile.ksh

Puis lancez-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 voir:

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 exécution de sourced, mais rien de vraiment lié ...

Vous pouvez même vérifier que $SECONDS est proche de 0.000, mais cela garantit que seuls les cas obtenus manuellement ...

Vous pouvez même essayer de vérifier si quel est le parent est:

Placez ceci 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 niveau de sous-sessions ...

Désolé, je n'ai pas trouvé de moyen fiable de le faire, sous ksh .

66
F. Hauri

La réponse BASH_SOURCE[] (bash-3.0 et versions ultérieures) semble la plus simple, bien que BASH_SOURCE[] soit non documenté pour fonctionner en dehors du corps d'une fonction _ (il se trouve que cela 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]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 main et source distinguent le contexte de l'appelant. L'utilisation de FUNCNAME[] vous permet de capturer et d'analyser la sortie caller. Pour être correct, vous devez connaître ou calculer la profondeur de votre appel local. Des cas tels qu'un script provenant de l'intérieur d'une autre fonction ou d'un autre script vont approfondir le tableau (pile). (FUNCNAME est une variable de tableau bash spéciale, elle devrait avoir des index contigus correspondant à la pile d'appels, tant qu'elle n'est jamais unset.)

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

(Dans bash-4.2 et les versions ultérieures, vous pouvez utiliser le formulaire plus simple ${FUNCNAME[-1]} à la place du 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 dans lequel je ne veux pas qu'il appelle" exit "s'il est recherché". L'idiome bash commun à cette situation est:

return 2>/dev/null || exit

Si le script est en cours d’alimentation, return le terminera et reviendra à l’appelant.

Si le script est en cours d'exécution, return renverra une erreur (redirigé) et exit mettra fin au script normalement. return et exit peuvent tous deux prendre un code de sortie, si nécessaire.

Malheureusement, cela ne fonctionne pas dans ksh (du moins pas dans la version dérivée d'AT & T que j'ai ici), il traite return comme équivalent à exit s'il est appelé en dehors d'une fonction ou d'un script à points.

Updated: Ce que vous {pouvez} faites dans les versions contemporaines de ksh consiste à vérifier la variable spéciale .sh.level qui est définie sur la profondeur d'appel de la fonction. Pour un script appelé, cela sera initialement désactivé, pour un script basé sur des 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 appeler issourced() dans le fichier que vous testez à partir du niveau supérieur ou à une profondeur de fonction connue.

(Vous pouvez également être intéressé par ce code sur github qui utilise une fonction de discipline ksh et quelques astuces de débogage pour émuler le tableau bash FUNCNAME.)

La réponse canonique ici: http://mywiki.wooledge.org/BashFAQ/109 propose également $- comme 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" ( écrasant le caractère intégré ), 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 puisse trouver ne concerne que pdksh, où chaque recherche d'un script ouvre un nouveau descripteur de fichier (commençant par 10 pour le script d'origine). Presque certainement pas quelque chose sur lequel vous voulez compter ... 
30
mr.spuratic

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

TL; DR

Essayez d'exécuter une instruction return. Si le script ne provient pas, cela générera une erreur. Vous pouvez détecter cette erreur et procéder selon vos besoins.

Mettez ceci dans un fichier et appelez-le, par exemple 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.

Le source:

Shell-Prompt> source test.sh
output: This script is sourced.

Pour moi, cela fonctionne en zsh et bash.

Explication

L'instruction return provoquera une erreur si vous essayez de l'exécuter en dehors d'une fonction ou si le script n'est pas généré. Essayez ceci à partir d'une invite de 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

Maintenant, vérifiez le code de sortie. 0 signifie OK (aucune erreur ne s'est produite), 1 signifie qu'une erreur s'est produite:

Shell-Prompt> echo $?
output: 1

Vous souhaitez également exécuter l'instruction return à l'intérieur d'un sous-shell. Lorsque l'instruction return l'exécute. . . bien . . . résultats. Si vous l'exécutez dans un sous-shell, il sera renvoyé par ce sous-shell plutôt que par votre script. Pour l'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 s'est produite à l'intérieur du sous-shell:

Shell-Prompt> echo $?
output: 1
18
user5754163

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

Cela fonctionne pour tous les scripts qui commencent par #!/bin/bashmais peuvent également provenir de différents shells.

#!/bin/bash

# Function definitions (API) and Shell variables (constants) go here

main()
{
# The script's execution part goes here
}

unset BASH_SOURCE 2>/dev/null
test ".$0" != ".$BASH_SOURCE" || main "$@"

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

  • Si exécuté par bash, main est appelé.
  • Si la source est bash, main n'est appelé que si le script appelant porte le même nom. (Par exemple, s'il se source.)
  • Si la source est un shell autre que bash, main n'est pas appelé.
  • S'il est exécuté par un shell autre que bash, main n'est pas appelé.
  • Si évalué par bash avec eval (eval "`cat script`"tous les guillemets sont importants!) Ne venant pas directement de la ligne de commande, cela appelle main. Pour toutes les autres variantes de eval, main n'est pas appelé.

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

  • Et cela ne repose pas sur un comportement non documenté qui pourrait changer.

Ainsi, à l'exception de certains cas improbables, main n'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, difficile à comprendre.

Comme BASH_SOURCE ne peut pas être désactivé dans bash, mais dans tous les autres shells, ceci intercepte également le cas Edge où BASH_SOURCE est défini sur $0.

Notez que cela ressemble beaucoup au code Python:

if __== '__main__': main()

Ce qui empêche également l’appel de main, sauf dans certains cas, comme vous pouvez importer/charger le script et appliquer ce __name__='__main__'

Pourquoi je pense que c'est un bon moyen général de résoudre le problème

Si vous avez quelque chose qui peut être recherché par plusieurs coques, il doit être compatible. Cependant (lisez les autres réponses), comme il n’existe aucun moyen portable de détecter la variable sourceing, vous devez modifier les règles .

En imposant que le script doit être exécuté par /bin/bash, vous procédez exactement de la sorte.

Ceci résout tous les cas mais après , auquel cas le script ne peut pas être exécuté directement:

  • /bin/bash n'est pas installé ou ne fonctionne pas (i. E. dans un environnement d'initialisation)
  • Si vous le dirigez vers un shell non bash comme dans curl https://example.com/script | $Shell$Shell n'est pas bash

Cependant, je ne peux pas penser à une raison réelle pour laquelle vous en avez besoin, ni à la possibilité de générer le même script en parallèle! Habituellement, vous pouvez l'envelopper, de telle sorte que le script soit toujours recherché. Puis exécutez la main à la main. Comme ça:

  • sh -c '. script && main'
  • echo 'eval "`curl https://example.com/script`" && main' | $Shell

Important:

Ce dernier exécute main deux fois, si $Shell exécute bash et que la ligne n'est pas exécutée à partir de la ligne de commande. (Mais je ne peux vraiment pas penser à une raison pour laquelle vous devriez jamais utiliser cela dans un script, sauf pour tromper le code exprès.)

Remarque

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 fait poster ceci.

6
Tino

Je vais donner une réponse spécifique à BASH. Korn Shell, désolé. Supposons que votre nom de script est include2.sh; faites ensuite une fonction inside le include2.sh appelé am_I_sourced. Voici ma version démo 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

Maintenant, essayez de l'exécuter de plusieurs manières:

~/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

Donc, cela fonctionne sans exception, et il n’utilise pas le matériel fragile $_. Cette astuce utilise la fonction d’introspection de BASH, c’est-à-dire les variables intégrées FUNCNAME et BASH_SOURCE; voir leur documentation en page de manuel bash. 

Seulement deux mises en garde:

1) L’appel à am_I_called doit a lieu dans le script recherché, mais pas dans aucune fonction, de façon à ce que ${FUNCNAME[1]} renvoie autre chose. Ouais ... tu aurais pu vérifier ${FUNCNAME[2]} - mais tu ne fais que rendre la vie plus dure.

2) la fonction am_I_called doit résider dans le script source si vous souhaitez connaître le nom du fichier en cours d’inclusion.

5
Wirawan Purwanto

Je voudrais suggérer une petite correction à la réponse très utile de Dennis , pour le 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 (à mon humble avis rémanent) Debian compatible POSIX Shell, dash. En outre, vous pouvez avoir besoin des guillemets pour vous protéger des noms de fichiers contenant des espaces, toujours dans ledit shell.

4
user354193

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
3
jim mcnamara

$_ est assez fragile. Vous devez le vérifier comme la première chose que vous faites dans le script. Et même dans ce cas, il n’est pas garanti qu’il contienne le nom de votre shell (si fourni) ou le nom du script (s’il est exécuté).

Par exemple, si l'utilisateur a défini BASH_ENV, alors au sommet d'un script, $_ contient le nom de la dernière commande exécutée dans le script BASH_ENV.

Le meilleur moyen que j'ai trouvé est d'utiliser $0 comme ceci:

name="myscript.sh"

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

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

Malheureusement, cette méthode ne fonctionne pas immédiatement dans zsh, car l'option functionargzero en fait plus que son nom ne le suggère, et elle est activée par défaut.

Pour contourner ce problème, je mets unsetopt functionargzero dans mon .zshenv.

2
Mikel

J'ai suivi mklement0 expression compacte .

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

/bin/ksh -c ./myscript.sh

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

/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 fini 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 un soutien coquilles exotiques :)

1
Stéphane Desneux

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

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

Je ne pense pas qu'il existe un moyen portable de faire cela en ksh et en bash. En bash, vous pourriez le détecter en utilisant la sortie caller, mais je ne pense pas qu'il existe un équivalent en ksh.

0
Michal Čihař

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

Lorsque vous utilisez curl ... | bash -s -- ARGS pour exécuter un script distant à la volée, le $ 0 sera simplement bash au lieu de /bin/bash lors de l’exécution du fichier de script réel; j’utilise donc 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
osexp2003

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é acheté 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 PID et ainsi de suite, ce qui n’est pas sûr car c’est quelque choseVARIABLE), vous devriez:

  • étendre les possibilités de votre si 
  • en utilisant switch/case, si vous voulez.

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



Par exemple:  

Lorsque vous sourcez un script via une session SSH, la valeur renvoyée par la variable "$ 0" (lorsque vous utilisez 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

OR

#!/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
0
ivanleoncz