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é?
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).
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 ..."
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.
[ "$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
$_ == $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
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 .
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:
FUNCNAME[]
, mais tant que seul le dernier élément de ce tableau est testé, il n'y a aucune ambiguïté.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 ... 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
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/bash
mais 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:
bash
, main
est appelé.bash
, main
n'est appelé que si le script appelant porte le même nom. (Par exemple, s'il se source.)bash
, main
n'est pas appelé.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
).
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__'
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 source
ing, 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)curl https://example.com/script | $Shell
où $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écutebash
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.)
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.
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.
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.
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
$_
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
.
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 :)
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 "$@"
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.
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
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:
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