Faire la différence entre l'exécution et le sourcing dans un script shell bash?

22

Soit ce que je demande ici est extrêmement peu orthodoxe / non conventionnel / risqué, soit mes compétences Google-fu ne sont tout simplement pas à la hauteur ...

Dans un bashscript shell, y a-t-il un moyen simple de dire s'il provient d'un autre script shell ou s'il est exécuté par lui-même? En d'autres termes, est-il possible de différencier les deux comportements suivants?

# from another shell script
source myScript.sh

# from command prompt, or another shell script
./myScript.sh

Ce que je pense faire, c'est de créer un script shell de type utilitaire contenant des bashfonctions qui peuvent être rendues disponibles une fois sourcées. Lorsque ce script est exécuté par lui-même, j'aime qu'il effectue également certaines opérations, en fonction des fonctions définies. Existe-t-il une sorte de variable d'environnement sur laquelle ce script shell peut s'appuyer, par exemple

some_function() {
    # ...
}
if [ -z "$IS_SOURCED" ]; then
    some_function;
fi

De préférence, je recherche une solution qui ne nécessite pas le script d'appelant pour définir des variables d'indicateur.

edit : Je connais la différence entre l'approvisionnement et l'exécution du script, ce que j'essaie de découvrir ici s'il est possible de faire la différence dans le script utilisé (dans les deux sens).

hjk
la source
1
doublon possible du script en cours
cuonglm
@cuonglm a édité ma question, je connais les différences entre les deux mais je me demande si je peux aussi faire par programmation le script shell faire la différence.
hjk
4
@cuonglm: Pas un doublon. Il ne pose aucune question sur la .commande, mais sur la détection si un script a été généré ou exécuté normalement (c'est-à-dire dans un sous-shell).
Jander
Très bonnes réponses sur la même question au débordement de pile: stackoverflow.com/a/28776166/96944
Jannie Theunissen

Réponses:

19

Oui - la variable $ 0 donne le nom du script tel qu'il a été exécuté:

$ cat example.sh
#!/bin/bash
script_name=$( basename ${0#-} ) #- needed if sourced no path
this_script=$( basename ${BASH_SOURCE} )
if [[ ${script_name} = ${this_script} ]] ; then
    echo "running me directly"
else
    echo "sourced from ${script_name}"
fi 

$ cat example2.sh
#!/bin/bash
. ./example.sh

Qui fonctionne comme:

$ ./example.sh
running me directly
$ ./example2.sh
example.sh sourced from example2.sh

Cela ne répond pas à la source d'un shell interactif, mais vous avez cette idée (j'espère).

Mis à jour pour inclure BASH_SOURCE - merci hjk

Cœur sombre
la source
Assez proche. :) Petit obstacle dont je vais avoir besoin de spécifier le nom du script au moins, mais je prendrai cette réponse s'il n'y a pas d'autres solutions viables ...
hjk
7

La combinaison de la réponse de @ DarkHeart avec la variable d'environnement BASH_SOURCEsemble faire l'affaire:

$ head example*.sh
==> example2.sh <==
#!/bin/bash
. ./example.sh

==> example.sh <==
#!/bin/bash
if [ "$(basename $0)" = "$(basename $BASH_SOURCE)" ]; then
    echo "running directly"
else
    echo "sourced from $0"
fi
$ ./example2.sh
sourced from ./example2.sh
$ ./example.sh
running directly

edit Semble encore être une solution plus simple si je devais simplement compter le nombre d'éléments dans BASH_SOURCEle tableau de:

if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "running directly"; else echo "sourced from $0"; fi
hjk
la source
1
On dirait que nous avons trouvé la variable 'bash_source' en même temps. :)
DarkHeart
@DarkHeart J'ai ajouté à ma réponse l'usage de compter la taille du tableau aussi ... merci de m'avoir fait allusion sur ce chemin! : D
hjk
1

Je viens de créer le même type de script de bibliothèque qui fonctionne beaucoup comme BusyBox. Dans ce document, j'utilise la fonction suivante pour tester si elle provient ...

function isSourced () {
  [[ "${FUNCNAME[1]}" == "source" ]]  && return 0
  return 1
}

Le tableau FUNCNAME géré par Bash est essentiellement une pile d'appels de fonction. $FUNCNAME(ou ${FUNCNAME[0]}) est le nom de la fonction en cours d'exécution. ${FUNCNAME[1]}est le nom de la fonction qui l'a appelée, etc.

L'élément le plus haut est une valeur spéciale pour le script lui-même. Il contiendra ...

  • le mot «source» si le script provient
  • le mot "principal" si le script est en cours d'exécution ET que le test est effectué à partir d'une fonction
  • "" (null) si le script est en cours d'exécution ET que le test est effectué en dehors de toute fonction, c'est-à-dire ... au niveau du script lui-même.

La fonction ci-dessus ne fonctionne réellement que lorsqu'elle est appelée au niveau du script (ce qui est tout ce dont j'avais besoin). Il échouerait s'il était appelé depuis une autre fonction car le numéro d'élément du tableau serait incorrect. Pour le faire fonctionner n'importe où, il faut trouver le haut de la pile et tester cette valeur, ce qui est plus compliqué.

Si vous en avez besoin, vous pouvez obtenir le numéro d'élément de tableau du "haut de la pile" avec ...

  local _top_of_stack=$(( ${#FUNCNAME[@]} - 1 ))

${#FUNCNAME[@]}est le nombre d'éléments dans le tableau. En tant que tableau à base zéro, nous soustrayons 1 pour obtenir le dernier élément #.

Ces trois fonctions sont utilisées ensemble pour produire une trace de pile de fonctions similaire à celle de Python et elles peuvent vous donner une meilleure idée de la façon dont tout cela fonctionne ...

function inspFnStack () {
  local T+="  "
  local _at=
  local _text="\n"
  local _top=$(inspFnStackTop)
  local _fn=${FUNCNAME[1]}; [[ $_fn =~ source|main ]]  || _fn+="()"
  local i=_top; ((--i))
  #
  _text+="$i item function call stack for $_fn ...\n"
  _text+="| L   BASH_SOURCE{BASH_LINENO called from}.FUNCNAME  \n"
  _text+="| ---------------------------------------------------\n"
  while (( $i > 0 ))
  do
    _text+="| $i ${T}$(inspFnStackItem $i)\n"
    T+="  "
    ((--i))
  done
  #
  printf "$_text\n"
  #
  return 0
}

function inspFnStackItem ()  {
  local _i=$1
  local _fn=${FUNCNAME[$_i]}; [[ $_fn =~ source|main ]]  || _fn+="()"
  local _at="${BASH_LINENO[$_i-1]}"; [[ $_at == 1 ]]  && _at="trap"
  local _item="${BASH_SOURCE[$_i]}{${_at}}.$_fn"
  #
  printf "%s" "$_item"
  return 0
}

function inspFnStackTop () {
  # top stack item is 1 less than length of FUNCNAME array stack
  printf "%d\n" $(( ${#FUNCNAME[@]} - 1 ))
  #
  return 0
}

Notez que FUNCNAME, BASH_SOURCE et BASH_LINENO sont 3 tableaux maintenus par bash comme s'il s'agissait d'un tableau en trois dimensions.

DocSalvager
la source
0

Je veux juste ajouter que le comptage du tableau semble peu fiable et il ne faut probablement pas supposer qu'il a sourceété utilisé car l'utilisation d'un point ( .) est également très courante (et antérieure au sourcemot - clé).

Par exemple, pour un sourced.shscript contenant uniquement echo $0:


$ . sourced.sh 
bash
$ source sourced.sh 
bash
$ chmod +x sourced.sh 
$ ./sourced.sh 
./sourced.sh
$ cat ./sourced.sh 
echo $0

Les solutions de comparaison suggérées fonctionnent mieux.

ka1l
la source
0

Une façon qui fonctionne également lors de l'approvisionnement à partir d'un shell interactif :

if [ $BASH_LINENO -ne 0 ]; then
    some_function;
fi

La BASH_LINENOvariable est également un tableau avec toutes les lignes où la fonction appelante a été exécutée. Il sera nul si vous appelez directement le script, ou un entier correspondant à un numéro de ligne.

La documentation BASH_ * variable

Borisu
la source