¿Diferenciar entre ejecutar y obtener un script de shell bash?

22

O lo que estoy preguntando aquí es extremadamente poco ortodoxo / poco convencional / arriesgado, o mis habilidades de Google-fu simplemente no están a la altura ...

En un bashscript de shell, ¿hay alguna manera fácil de saber si otro script de shell lo obtiene o se ejecuta solo? En otras palabras, ¿es posible diferenciar entre los siguientes dos comportamientos?

# from another shell script
source myScript.sh

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

Lo que pienso hacer es crear un script de shell similar a las utilidades que contenga bashfunciones que puedan estar disponibles cuando se obtengan. Sin embargo, cuando este script se ejecute solo, me gustaría que realice ciertas operaciones, también en función de las funciones definidas. ¿Hay algún tipo de variable de entorno que este script de shell pueda detectar, p. Ej.

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

Preferiblemente, estoy buscando una solución que no requiera que el script de llamada establezca ninguna variable de marca.

editar : Sé la diferencia entre el abastecimiento y la ejecución del script, lo que estoy tratando de descubrir aquí si es posible distinguir la diferencia en el script que se está utilizando (en ambos sentidos).

hjk
fuente
1
posible duplicado del script en ejecución con "." y con "fuente"
cuonglm
@cuonglm editó mi pregunta, conozco las diferencias entre ambos, pero me pregunto si puedo hacer que el script de shell también marque la diferencia mediante programación.
hjk
44
@cuonglm: no es un duplicado. No está preguntando sobre el .comando en absoluto, sino sobre la detección de si un script se ha originado o ejecutado normalmente (es decir, en una subshell).
Jander
Muy buenas respuestas sobre la misma pregunta en el desbordamiento de la pila: stackoverflow.com/a/28776166/96944
Jannie Theunissen

Respuestas:

19

Sí, la variable $ 0 da el nombre del script tal como se ejecutó:

$ 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

Que funciona como:

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

Eso no sirve para ser fuente de un shell interactivo, pero se entiende esta idea (espero).

Actualizado para incluir BASH_SOURCE - gracias hjk

Corazón oscuro
fuente
Suficientemente cerca. :) Pequeño obstáculo que necesitaré para especificar el nombre del script al menos, pero tomaré esta respuesta si no hay otras soluciones viables ...
hjk
7

La combinación de la respuesta de @ DarkHeart con la variable de entorno BASH_SOURCEparece hacer el truco:

$ 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 Parece ser una solución más simple aún si tuviera que contar la cantidad de elementos en BASH_SOURCEla matriz:

if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "running directly"; else echo "sourced from $0"; fi
hjk
fuente
1
Parece que encontramos la variable 'bash_source' al mismo tiempo. :)
DarkHeart
@DarkHeart He agregado a mi respuesta el uso de contar el tamaño de la matriz también ... ¡gracias por insinuarme en este camino! : D
hjk
1

Acabo de crear el mismo tipo de script de biblioteca que funciona mucho como BusyBox. En él, utilizo la siguiente función para probar si se está obteniendo ...

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

La matriz FUNCNAME mantenida por Bash es esencialmente una pila de llamadas de función. $FUNCNAME(o ${FUNCNAME[0]}) es el nombre de la función que se está ejecutando actualmente. ${FUNCNAME[1]}es el nombre de la función que lo llamó, y así sucesivamente.

El elemento superior es un valor especial para el script en sí. Contendrá ...

  • la palabra "fuente" si el script se obtiene
  • la palabra "main" si el script se ejecuta Y la prueba se realiza desde una función
  • "" (nulo) si el script se está ejecutando Y la prueba se está realizando fuera de cualquier función, es decir ... al nivel del script en sí.

La función anterior en realidad solo funciona cuando se llama a nivel de script (que es todo lo que necesitaba). Fallaría si se llama desde otra función porque el número de elemento de la matriz sería incorrecto. Para que funcione en cualquier lugar requiere encontrar la parte superior de la pila y probar ese valor, lo cual es más complicado.

Si lo necesita, puede obtener el número de elemento de la matriz de la "parte superior de la pila" con ...

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

${#FUNCNAME[@]}es el número de elementos en la matriz. Como una matriz basada en cero, restamos 1 para obtener el último elemento #.

Estas tres funciones se usan juntas para producir un seguimiento de la pila de funciones similar al de Python y pueden darle una mejor idea de cómo funciona todo esto ...

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
}

Tenga en cuenta que FUNCNAME, BASH_SOURCE y BASH_LINENO son 3 matrices mantenidas por bash como si fueran una matriz tridimensional.

DocSalvager
fuente
0

Solo quiero agregar que contar el conjunto parece no ser confiable y uno probablemente no debería suponer que sourcese usó ya que usar un punto ( .) también es muy común (y es anterior a la sourcepalabra clave).

Por ejemplo, para un sourced.shscript que contiene solo echo $0:


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

Las soluciones de comparación sugeridas funcionan mejor.

ka1l
fuente
0

Una forma que también funciona cuando se obtiene de un shell interactivo :

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

La BASH_LINENOvariable también es una matriz con todas las líneas en las que se ejecutó la función de llamada. Será cero si llama al script directamente, o un número entero correspondiente a un número de línea.

Los documentos variables BASH_ *

Borisu
fuente