Cómo detectar si se está obteniendo un script

217

Tengo un script en el que no quiero que llame exitsi se está buscando.

Pensé en comprobar si $0 == bashesto tiene problemas si el script proviene de otro script o si el usuario lo obtiene de un shell diferente ksh.

¿Hay alguna forma confiable de detectar si se está obteniendo un script?

brianegge
fuente
2
Tuve un problema similar hace un tiempo y lo resolví evitando la "salida" en todos los casos; "kill -INT $$" finaliza el script de forma segura en cualquier caso.
JESii
1
¿Notaste esta respuesta ? Se administra 5 años después de lo aceptado, pero tiene "baterías incluidas".
raratiru

Respuestas:

73

Esto parece ser portátil entre Bash y Korn:

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

Una línea similar a esta o una tarea como pathname="$_"(con una prueba y acción posterior) debe estar en la primera línea del guión o en la línea después del shebang (que, si se usa, debe ser para ksh para que funcione bajo La mayoría de las circunstancias).

Pausado hasta nuevo aviso.
fuente
10
Lamentablemente, no está garantizado que funcione. Si el usuario lo ha configurado BASH_ENV, $_en la parte superior del script se ejecutará el último comando BASH_ENV.
Mikel
30
Esto tampoco funcionará si usa bash para ejecutar el script, por ejemplo, $ bash script.sh, entonces $ _ sería / bin / bash en lugar de ./script.sh, que es el caso que espera, cuando se invoca el script de esta manera: $ ./script.sh En cualquier caso, detectar con $_es un problema.
Wirawan Purwanto
2
Se podrían incluir pruebas adicionales para verificar esos métodos de invocación.
Pausado hasta nuevo aviso.
8
Por desgracia, eso está mal! mira mi respuesta
F. Hauri
8
Para resumir: si bien este enfoque generalmente funciona, no es robusto ; falla en los siguientes 2 escenarios: (a) bash script(invocación a través del ejecutable del shell, que esta solución informa erróneamente como fuente ), y (b) (mucho menos probable) echo bash; . script(si $_coincide con el shell que origina el script, esta solución lo informa erróneamente como una subshell ). Solo las variables especiales específicas de shell (por ejemplo, $BASH_SOURCE) permiten soluciones robustas (se deduce que no existe una solución robusta compatible con POSIX). Se es posible, aunque engorroso, para elaborar una prueba cruzada shell robusto.
mklement0
170

Si su versión de Bash conoce la variable de matriz BASH_SOURCE, intente algo como:

# 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 ..."
barroyo
fuente
11
Esa es quizás la forma más limpia, ya que $ BASH_SOURCE está destinado exactamente para ese propósito.
con-f-use
44
Tenga en cuenta que esto no funcionará bajo ksh, que es una condición que especificó el OP.
Pausado hasta nuevo aviso.
2
¿Hay alguna razón para usar en ${BASH_SOURCE[0]}lugar de solo $BASH_SOURCE? Y ${0}vs $0?
hraban
44
BASH_SOURCEes una variable de matriz (consulte el manual ) que contiene un seguimiento de pila de fuentes, donde ${BASH_SOURCE[0]}es la última. Las llaves se usan aquí para decirle a bash qué es parte del nombre de la variable. No son necesarios $0en este caso, pero tampoco duelen. ;)
Konrad el
44
@Konrad, y si te expandes, $arrayobtienes ${array[0]}por defecto. Entonces, de nuevo, ¿hay alguna razón [...]?
Charles Duffy
133

Soluciones sólidas para bash, ksh,zsh , incluyendo una cruz-shell uno, además de una solución compatible con POSIX razonablemente robusto :

  • Los números de versión dados son los más funcionalidad en el que se verifica - probable es que estas soluciones funcionan en las versiones anteriores tanto, también - la retroalimentación de bienvenida .

  • Usando solo las características de POSIX (como en dash, que actúa como /bin/shen Ubuntu), no hay una manera sólida de determinar si se está obteniendo un script; consulte a continuación la mejor aproximación .

Siguen una frase : la explicación a continuación; la versión cross-shell es compleja, pero debería funcionar de manera robusta:

  • bash (verificado en 3.57 y 4.4.19)

    (return 0 2>/dev/null) && sourced=1 || sourced=0
  • ksh (verificado en 93u +)

    [[ $(cd "$(dirname -- "$0")" && 
       printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
         sourced=1 || sourced=0
    
  • zsh (verificado en 5.0.5): asegúrese de llamar a esto fuera de una función

    [[ $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
    
  • Compatible con POSIX ; No es una línea (tubería única) por razones técnicas y no es completamente robusta (ver abajo):

    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
    

Explicación:


intento

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

Nota: La técnica se adaptó de la respuesta del usuario5754163 , ya que resultó ser más robusta que la solución original, [[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0[1]

  • Bash permite returndeclaraciones solo de funciones y, en el ámbito de nivel superior de un script, solo si el script tiene su origen .

    • Si returnse usa en el ámbito de nivel superior de un script sin fuente , se emite un mensaje de error y el código de salida se establece en 1.
  • (return 0 2>/dev/null)se ejecuta returnen una subshell y suprime el mensaje de error; luego, el código de salida indica si la secuencia de comandos se obtuvo ( 0) o no ( 1), que se utiliza con los operadores &&y ||para establecer la sourcedvariable en consecuencia.

    • El uso de un subshell es necesario, porque la ejecución returnen el ámbito de nivel superior de un script de origen saldría del script.
    • Punta del sombrero para @Haozhun , quien hizo el comando más robusto al usarlo explícitamente 0como returnoperando; señala: por bash ayuda de return [N]: "Si se omite N, el estado de retorno es el del último comando". Como resultado, la versión anterior [que se usaba simplemente return, sin un operando] produce un resultado incorrecto si el último comando en el shell del usuario tiene un valor de retorno distinto de cero.

ksh

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

La variable especial ${.sh.file}es algo análoga a $BASH_SOURCE; tenga en cuenta que ${.sh.file}causa un error de sintaxis en bash, zsh y dash, así que asegúrese de ejecutarlo condicionalmente en scripts de múltiples shell.

A diferencia de bash, $0y ${.sh.file}NO se garantiza que sean exactamente idénticos en el caso sin origen, ya que $0puede ser una ruta relativa , aunque ${.sh.file}siempre es una ruta completa , por lo que $0debe resolverse en una ruta completa antes de comparar.


zsh

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

$ZSH_EVAL_CONTEXTcontiene información sobre el contexto de evaluación; llame a esto fuera de una función. Dentro de una secuencia de comandos de origen [alcance de nivel superior], $ZSH_EVAL_CONTEXT termina con :file.

Advertencia: Dentro de una sustitución de orden, APPENDs zsh :cmdsubst, por lo que la prueba $ZSH_EVAL_CONTEXTde :file:cmdsubst$allí.


Usar solo las funciones de POSIX

Si está dispuesto a hacer ciertas suposiciones, puede hacer una suposición razonable, pero no infalible, de si su secuencia de comandos se obtiene, basándose en conocer los nombres de archivo binarios de los shells que pueden estar ejecutando su secuencia de comandos .
En particular, esto significa que este enfoque falla si su secuencia de comandos se obtiene de otra secuencia de comandos .

La sección "Cómo manejar las invocaciones de origen" en esta respuesta mía discute los casos extremos que no se pueden manejar con las características POSIX solo en detalle.

Esto se basa en el comportamiento estándar de $0, que zsh, por ejemplo, no exhibe.

Por lo tanto, el enfoque más seguro es combinar los métodos robustos específicos de la carcasa anteriores con una solución alternativa para todas las conchas restantes.

Punta del sombrero para Stéphane Desneux y su respuesta para la inspiración (transformando mi expresión de declaración de concha cruzada en una declaración shcompatible ify agregando un controlador para otras conchas).

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 descubrió que [[ $0 != "$BASH_SOURCE" ]]produce un falso positivo cuando ejecuta un script ubicado en al$PATH pasar su simple nombre de archivo al bashbinario; por ejemplo, bash my-scriptporque $0es justo my-script, mientras que $BASH_SOURCEes la ruta completa . Mientras que normalmente no utilizar esta técnica para invocar los scripts en el $PATH- que acababa de invocarlos directamente ( my-script) - que es muy útil cuando se combina con el -xde la depuración .

mklement0
fuente
1
Felicitaciones por una respuesta tan completa.
DrUseful
75

Después de leer la respuesta de @ DennisWilliamson, hay algunos problemas, ver a continuación:

Como esta pregunta representa y , hay otra parte en esta respuesta sobre ... vea abajo.

Sencillo camino

[ "$0" = "$BASH_SOURCE" ]

Probemos (sobre la marcha porque esa fiesta podría ;-):

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)

En su sourcelugar, uso off .para facilitar la lectura (como .es un alias 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)

Tenga en cuenta que el número de proceso no cambia mientras el proceso permanece en origen :

echo $$
29301

¿Por qué no usar la $_ == $0comparación?

Para asegurar muchos casos, comienzo a escribir un script verdadero :

#!/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)"

Copie esto a un archivo llamado testscript:

cat >testscript   
chmod +x testscript

Ahora podríamos probar:

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

Está bien.

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

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

Está bien.

Pero, para probar un script antes de agregar una -xbandera:

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

O para usar variables predefinidas:

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)

Esto ya no funcionará.

Mover el comentario de la 5ta línea a la 6ta daría una respuesta más legible:

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

Más fuerte: ahora...

Como no uso mucho, después de leer algo en la página del manual, están mis intentos:

#!/bin/ksh

set >/tmp/ksh-$$.log

Copia esto en un testfile.ksh:

cat >testfile.ksh
chmod +x testfile.ksh

Que ejecutarlo dos veces:

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

y ver:

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

Hay alguna variable heredada en una ejecución de origen , pero nada realmente relacionado ...

Incluso podría verificar que $SECONDSesté cerca 0.000, pero eso garantiza que solo los casos de origen manual ...

Incluso podría intentar verificar qué es el padre:

Coloque esto en su 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

o ps ho cmd $PPID, pero esto funciona solo para un nivel de subsesiones ...

Lo siento, no pude encontrar una manera confiable de hacerlo, bajo .

F. Hauri
fuente
[ "$0" = "$BASH_SOURCE" ] || [ -z "$BASH_SOURCE" ]para scripts leídos a través de pipe ( cat script | bash).
Hakre
2
Tenga en cuenta que .no es un alias source, en realidad es al revés. source somescript.shes un Bash-ism y no es portátil, . somescript.shes POSIX y IIRC portátil.
dragon788
32

La BASH_SOURCE[]respuesta (bash-3.0 y posterior) parece más simple, aunque noBASH_SOURCE[] está documentada para trabajar fuera del cuerpo de una función (actualmente funciona, en desacuerdo con la página del manual).

La forma más sólida, como lo sugiere Wirawan Purwanto, es verificar FUNCNAME[1] dentro de una función :

function mycheck() { declare -p FUNCNAME; }
mycheck

Luego:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

Esto es equivalente a verificar la salida de callerlos valores mainy sourcedistinguir el contexto del llamante. El uso le FUNCNAME[]ahorra capturar y analizar callerresultados. Sin embargo, debe saber o calcular la profundidad de su llamada local para ser correcta. Casos como un script que se obtiene de otra función o script harán que la matriz (pila) sea más profunda. ( FUNCNAMEes una variable de matriz de bash especial, debe tener índices contiguos correspondientes a la pila de llamadas, siempre que nunca sea así unset).

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

(En bash-4.2 y versiones posteriores, puede utilizar la forma más simple ${FUNCNAME[-1]}para el último elemento de la matriz. Mejorado y simplificado gracias al comentario de Dennis Williamson a continuación).

Sin embargo, su problema como se indica es " Tengo un script en el que no quiero que llame a 'salir' si se está obteniendo ". El bashidioma común para esta situación es:

return 2>/dev/null || exit

Si la secuencia de comandos se obtiene return, la terminará y volverá a la persona que llama.

Si el script se está ejecutando, returndevolverá un error (redirigido) y exitfinalizará el script de la forma habitual. Ambos returny exitpueden tomar un código de salida, si es necesario.

Lamentablemente, esto no funciona ksh(al menos no en la versión derivada de AT&T que tengo aquí), se trata returncomo equivalente a exitsi se invoca fuera de una función o script de origen de punto.

Actualizado : lo que puede hacer en las versiones contemporáneas kshes verificar la variable especial .sh.levelque se establece en la profundidad de la llamada a la función. Para una secuencia de comandos invocada, esto inicialmente no se establecerá, para una secuencia de comandos de origen de punto se establecerá en 1.

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

issourced && echo this script is sourced

Esto no es tan robusto como la versión bash, debe invocar issourced()en el archivo que está probando en el nivel superior o en una profundidad de función conocida.

(También puede estar interesado en este código en github que utiliza una kshfunción de disciplina y algunos trucos de trampa de depuración para emular la FUNCNAMEmatriz bash ).

La respuesta canónica aquí: http://mywiki.wooledge.org/BashFAQ/109 también se ofrece $-como otro indicador (aunque imperfecto) del estado de shell.


Notas:

  • es posible crear funciones bash denominadas "main" y "source" ( anulando el builtin ), estos nombres pueden aparecer FUNCNAME[]pero siempre que solo se pruebe el último elemento de esa matriz no hay ambigüedad.
  • No tengo una buena respuesta para pdksh. Lo más cercano que puedo encontrar se aplica solo pdksh, donde cada fuente de un script abre un nuevo descriptor de archivo (comenzando con 10 para el script original). Casi seguro que no es algo en lo que quieras confiar ...
Sr. púrpura
fuente
¿Qué tal ${FUNCNAME[(( ${#FUNCNAME[@]} - 1 ))]}obtener el último elemento (inferior) en la pila? Entonces, probar contra "main" (negar para OP) fue lo más confiable para mí.
Adrian Günter
Si tengo un PROMPT_COMMANDconjunto, eso aparece como el último índice de la FUNCNAMEmatriz si ejecuto source sourcetest.sh. La inversión de la verificación (en busca de mainque el último índice) parece más robusto: is_main() { [[ ${FUNCNAME[@]: -1} == "main" ]]; }.
dimo414
1
Los estados de la página de manual, que FUNCNAMEsolo están disponibles en funciones. Según mis pruebas con declare -p FUNCNAME, se bashcomporta de manera diferente. v4.3 da un error fuera de las funciones, mientras que v4.4 da declare -a FUNCNAME. Ambos (!) De retorno mainpara ${FUNCNAME[0]}en el script principal (si se ejecuta), mientras que $FUNCNAMEno da nada. Y: Hay tantos scripts por ahí "ab" que usan $BASH_SOURCEfunciones externas, que dudo que esto pueda o cambie.
Tino
24

Nota del editor: la solución de esta respuesta funciona de manera sólida, pero es solo bash. Se puede simplificar a
(return 2>/dev/null).

TL; DR

Intenta ejecutar una returndeclaración. Si el script no tiene origen, eso generará un error. Puede detectar ese error y proceder según lo necesite.

Ponga esto en un archivo y llámelo, por ejemplo, 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

Ejecútelo directamente:

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

Fuente:

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

Para mí, esto funciona en zsh y bash.

Explicación

La returndeclaración generará un error si intenta ejecutarlo fuera de una función o si el script no tiene origen. Pruebe esto desde un indicador de shell:

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

No necesita ver ese mensaje de error, por lo que puede redirigir la salida a dev / null:

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

Ahora verifique el código de salida. 0 significa OK (no se produjeron errores), 1 significa que se produjo un error:

shell-prompt> echo $?
output: 1

También desea ejecutar la returndeclaración dentro de un sub-shell. Cuando la returnsentencia lo ejecuta. . . bien . . . devoluciones. Si lo ejecuta en un sub-shell, saldrá de ese sub-shell, en lugar de regresar de su script. Para ejecutar en el sub-shell, envuélvalo en $(...):

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

Ahora, puede ver el código de salida del sub-shell, que debería ser 1, porque se generó un error dentro del sub-shell:

shell-prompt> echo $?
output: 1
usuario5754163
fuente
Esto falla para mí 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 no especifica qué returndebe hacer en el nivel superior ( pubs.opengroup.org/onlinepubs/9699919799/utilities/… ). El dashshell trata a returnen el nivel superior como exit. Otros shells les gusta basho zshno permiten returnen el nivel superior, que es la característica de una técnica como esta explota.
user5754163
Funciona en sh si elimina el $antes de la subshell. Es decir, use en (return >/dev/null 2>&1)lugar de $(return >/dev/null 2>&1), pero luego deja de funcionar en bash.
Eponymous
@Eponymous: dado que dash, donde esta solución no funciona, actúa como shen Ubuntu, por ejemplo, esta solución generalmente no funciona sh. La solución me funciona en Bash 3.2.57 y 4.4.5, con o sin el $antes (...)(aunque nunca hay una buena razón para ello $).
mklement0
2
returnSi no se ejecuta un valor de retorno explícito, se rompen sourcelas secuencias de comandos justo después de un comando con una salida incorrecta. Propuso la edición de mejora.
DimG
12

FWIW, después de leer todas las otras respuestas, se me ocurrió la siguiente solución:

Actualización: En realidad, alguien detectó un error corregido en otra respuesta que también afectó a la mía. Creo que la actualización aquí también es una mejora (vea las ediciones si tiene curiosidad).

Esto funciona para todos los scripts, que comienzan con#!/bin/bash diferentes shells, pero también pueden obtenerse para obtener información (como la configuración) que se mantiene fuera de la mainfunción.

Según los comentarios a continuación, esta respuesta aquí aparentemente no funciona para todas las bashvariantes. Tampoco para sistemas, donde /bin/shse basa bash. IE falla para bashv3.x en MacOS. (Actualmente no sé cómo resolver esto).

#!/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 "$@"

En lugar de las últimas 2 líneas, puede usar el siguiente código (en mi opinión, menos legible) para no establecer BASH_SOURCEen otros shells y permitir set -etrabajar en main:

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

Esta receta de script tiene las siguientes propiedades:

  • Si se ejecuta de bashla manera normal, mainse llama. Tenga en cuenta que esto no incluye una llamada como bash -x script(donde scriptno contiene una ruta), consulte a continuación.

  • Si se obtiene bash, mainsolo se llama, si el script de llamada tiene el mismo nombre. (Por ejemplo, si se origina a sí mismo o a través de bash -c 'someotherscript "$@"' main-script args..dónde main-scriptdebe estar, lo que testve como $BASH_SOURCE).

  • Si se obtiene / ejecuta / lee / evaled por un shell que no sea bash, mainno se llama ( BASH_SOURCEsiempre es diferente a $0).

  • mainno se llama si bashlee la secuencia de comandos de stdin, a menos que establezca $0la cadena vacía de esta manera:( exec -a '' /bin/bash ) <script

  • Si se evalúa con bashwith evaleval "`cat script`" todas las comillas son importantes! ) Desde algún otro script, esto llama main. Si evalse ejecuta directamente desde la línea de comandos, esto es similar al caso anterior, donde el script se lee desde stdin. ( BASH_SOURCEestá en blanco, mientras que, por lo $0general, /bin/bashno está obligado a algo completamente diferente).

  • Si mainno se llama, devuelve true( $?=0).

  • Esto no se basa en un comportamiento inesperado (anteriormente escribí indocumentado, pero no encontré documentación que unsettampoco pueda modificar BASH_SOURCE):

    • BASH_SOURCEes una matriz reservada de bash . Pero permitir BASH_SOURCE=".$0"cambiarlo abriría una lata de gusanos muy peligrosa, por lo que mi expectativa es que esto no debe tener efecto (excepto, tal vez, alguna advertencia fea aparece en alguna versión futura de bash).
    • No hay documentación que BASH_SOURCEfuncione fuera de las funciones. Sin embargo, lo contrario (que solo funciona en funciones) tampoco está documentado. La observación es que funciona (probado con bashv4.3 y v4.4, desafortunadamente ya no tengo bashv3.x) y que demasiados scripts se romperían si $BASH_SOURCEdeja de funcionar como se observa. Por lo tanto, mi expectativa es que se BASH_SOURCEmantenga igual que para las futuras versiones de bash.
    • En contraste (¡bonito hallazgo, por cierto!) Considere ( return 0 ), lo que da 0si es de origen y 1si no lo es. Esto es un poco inesperado, no solo para mí , y (de acuerdo con las lecturas allí) POSIX dice que eso returnde subshell es un comportamiento indefinido (y el returnaquí es claramente de un subshell). Quizás esta característica finalmente tenga un uso generalizado suficiente para que ya no se pueda cambiar, pero AFAICS existe una posibilidad mucho mayor de que alguna bashversión futura cambie accidentalmente el comportamiento de retorno en ese caso.
  • Lamentablemente bash -x script 1 2 3no se ejecuta main. (Comparar script 1 2 3donde scriptno tiene camino). Lo siguiente puede usarse como solución alternativa:

    • bash -x "`which script`" 1 2 3
    • bash -xc '. script' "`which script`" 1 2 3
    • Eso bash script 1 2 3no se mainpuede ejecutar como una característica.
  • Tenga en cuenta que las ( exec -a none script )llamadas main( bashno se transfieren $0al script, para esto debe usarlas -ccomo se muestra en el último punto).

De este modo, a excepción de algunos algunos casos de esquina, mainse llama solamente, cuando el script se ejecuta de la forma habitual. Normalmente esto es lo que desea, especialmente porque carece de código complejo difícil de entender.

Tenga en cuenta que es muy similar al código Python:

if __name__ == '__main__': main()

Lo que también evita la llamada main, excepto en algunos casos de esquina, ya que puede importar / cargar el script y aplicarlo__name__='__main__'

¿Por qué creo que esta es una buena forma general de resolver el desafío?

Si tiene algo, que puede obtenerse mediante múltiples shells, debe ser compatible. Sin embargo (lea las otras respuestas), ya que no hay una forma portátil (fácil de implementar) para detectar el sourceing, debe cambiar las reglas .

Al hacer cumplir que el script debe ser ejecutado /bin/bash, usted hace exactamente esto.

Esto resuelve todos los casos, pero a continuación, en cuyo caso el script no puede ejecutarse directamente:

  • /bin/bash no está instalado o no funciona (es decir, E. en un entorno de arranque)
  • Si lo canalizas a una concha como en curl https://example.com/script | $SHELL
  • (Nota: Esto solo es cierto si su bashversión es lo suficientemente reciente. Se informa que esta receta falla para ciertas variantes. Por lo tanto, asegúrese de verificar que funcione para su caso).

Sin embargo, ¡no puedo pensar en ninguna razón real en la que lo necesite y también en la capacidad de obtener exactamente el mismo script en paralelo! Por lo general, puede envolverlo para ejecutarlo maina mano. Como eso:

  • $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

Notas

  • ¡Esta respuesta no hubiera sido posible sin la ayuda de todas las otras respuestas! Incluso los incorrectos, lo que inicialmente me hizo publicar esto.

  • Actualización: Editado debido a los nuevos descubrimientos encontrados en https://stackoverflow.com/a/28776166/490291

Tino
fuente
Probado para ksh y bash-4.3. Agradable. Es una pena que su respuesta tenga una vida difícil dado que las otras respuestas ya llevaban años recogiendo votos.
hagello
gracias por esta respuesta Aprecié la prueba más larga y 'menos legible' con la declaración IF ya que es bueno manejar ambas situaciones para al menos dar una falla no silenciosa. En mi caso, necesito un script para obtenerlo o informar al usuario de su error al no usar la fuente.
Tim Richardson
@Tino: En cuanto a "también podría provenir de diferentes shells": en macOS, donde /bin/shestá efectivamente bashen el modo POSIX, la asignación BASH_SOURCE rompe el script. En otras conchas ( dash, ksh, zsh), invocando el script pasándolo como un argumento de archivo directamente a los ejecutables de concha mal funcionamiento (por ejemplo, zsh <your-script>hará que su escritura creer equivocadamente que es de origen ). (Ya que mencionas tubería de un mal funcionamiento del código, en todas las conchas.)
mklement0
@Tino: como un aparte: mientras que . <your-script>(el abastecimiento) funciona en principio en todos los shells tipo POSIX, solo tiene sentido si el script fue escrito explícitamente para usar solo las características de POSIX, para evitar que las características específicas de un shell rompan la ejecución en otras conchas; Por lo tanto, usar una línea Bash shebang (en lugar de #!/bin/sh) es confuso, al menos sin un comentario llamativo. Por el contrario, si su script está destinado a ejecutarse solo desde Bash (incluso si no se considera qué características pueden no ser portátiles), es mejor rechazar la ejecución en shells que no sean Bash.
mklement0
1
@ mklement0 Gracias de nuevo, agregué una nota de que hay un problema. Para otros lectores: cuando se obtiene con bash v3.x, no debe ejecutarse main, ¡pero lo hace en este caso! Y cuando se obtiene /bin/sh, es decir bash --posix, lo mismo sucede en este caso, y eso también es completamente incorrecto.
Tino
6

Esto funciona más adelante en el script y no depende de la variable _:

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

o

[ $(basename $0) = $Prog ] && exit
jim mcnamara
fuente
1
Creo que esta respuesta es una de las pocas que cumplen con POSIX aquí. Con las desventajas obvias de que tiene que conocer el nombre de archivo y no funciona si ambos scripts tienen el mismo nombre de archivo.
JepZ
5

Daré una respuesta específica de BASH. Korn shell, lo siento. Supongamos que su nombre de script es include2.sh; luego haga una función dentro de la include2.shllamada am_I_sourced. Aquí está mi versión demo 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

Ahora intenta ejecutarlo de muchas maneras:

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

Así que esto funciona sin excepción, y no está usando las $_cosas frágiles . Este truco utiliza la función de introspección de BASH, es decir, variables integradas FUNCNAMEy BASH_SOURCE; vea su documentación en la página del manual bash.

Solo dos advertencias:

1) la llamada a am_I_called debe llevarse a cabo en el script de origen, pero no dentro de ninguna función, para no ${FUNCNAME[1]}devolver otra cosa. Sí ... podrías haberlo comprobado ${FUNCNAME[2]}, pero solo haces tu vida más difícil.

2) la función am_I_called debe residir en el script de origen si desea saber cuál es el nombre del archivo que se incluye.

Wirawan Purwanto
fuente
1
Aclaración: esta función requiere BASH versión 3+ para funcionar. En BASH 2, FUNCNAME es una variable escalar en lugar de una matriz. Además, BASH 2 no tiene la variable de matriz BASH_SOURCE.
Wirawan Purwanto
4

Me gustaría sugerir una pequeña corrección a la muy útil respuesta de Dennis , para que sea un poco más portátil, espero:

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

porque [[no es reconocida por la (algo anal retentivo mi humilde opinión) Debian POSIX compatible cáscara, dash. Además, uno puede necesitar las comillas para protegerse contra los nombres de archivo que contienen espacios, nuevamente en dicho shell.

usuario354193
fuente
2

$_Es bastante frágil. Tienes que marcarlo como lo primero que haces en el script. E incluso entonces, no se garantiza que contenga el nombre de su shell (si se obtiene) o el nombre del script (si se ejecuta).

Por ejemplo, si el usuario ha configurado BASH_ENV, entonces en la parte superior de un script, $_contiene el nombre del último comando ejecutado en el BASH_ENVscript.

La mejor manera que he encontrado es usarlo $0así:

name="myscript.sh"

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

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

Desafortunadamente, esta forma no funciona de manera functionargzeropredeterminada en zsh debido a que la opción hace más de lo que su nombre sugiere y está activada de manera predeterminada.

Para evitar esto, puse unsetopt functionargzeromi .zshenv.

Mikel
fuente
1

Seguí mklement0 expresión compacta .

Eso está bien, pero noté que puede fallar en el caso de ksh cuando se invoca así:

/bin/ksh -c ./myscript.sh

(piensa que es de origen y no es porque ejecuta una subshell) Pero la expresión funcionará para detectar esto:

/bin/ksh ./myscript.sh

Además, incluso si la expresión es compacta, la sintaxis no es compatible con todos los shells.

Así que terminé con el siguiente código, que funciona para bash, zsh, dash y 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

Siéntase libre de agregar soporte de conchas exóticas :)

Stéphane Desneux
fuente
En ksh 93+u, ksh ./myscript.shfunciona bien para mí (con mi declaración): ¿qué versión estás usando?
mklement0
Me temo que no hay forma de determinar de manera confiable si un script se obtiene utilizando solo las características POSIX: su intento asume Linux ( /proc/$$/cmdline) y se enfoca dashsolo (que también actúa como shUbuntu, por ejemplo). Si está dispuesto a hacer ciertas suposiciones, puede examinar $0una prueba razonable, pero incompleta, que sea portátil.
mklement0
++ para el enfoque básico, sin embargo: me he tomado la libertad de adaptarlo para lo que creo que es la mejor aproximación portátil de apoyo sh/ dashtambién, en un anexo a mi respuesta.
mklement0
0

No creo que haya una forma portátil de hacer esto en ksh y bash. En bash, podría detectarlo usando la callersalida, pero no creo que exista un equivalente en ksh.

Michal Čihař
fuente
$0trabaja en bash, ksh93y pdksh. No tengo ksh88que probar.
Mikel
0

Necesitaba un one-liner que funcionara en [mac, linux] con bash.version> = 3 y ninguna de estas respuestas se ajustaba perfectamente.

[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"
Karsten
fuente
1
La bashsolución funciona bien (podría simplificarse $BASH_SOURCE), pero la kshsolución no es sólida: si su script está siendo obtenido por otro script , obtendrá un falso positivo.
mklement0
0

Directo al grano: debe evaluar si la variable "$ 0" es igual al nombre de su Shell.


Me gusta esto:

#!/bin/bash

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


Vía SHELL :

$ bash check_source.sh 
First Parameter: check_source.sh

The script WAS NOT sourced.

VÍA FUENTE :

$ source check_source.sh
First Parameter: bash

The script was sourced.



Es bastante difícil tener una forma 100% portátil de detectar si un script se originó o no.

Con respecto a mi experiencia (7 años con Shellscripting) , la única forma segura (no depender de las variables de entorno con PID, etc., que no es seguro debido a que es algo VARIABLE ), debe:

  • ampliar las posibilidades de su if
  • usando switch / case, si quieres.

Ambas opciones no se pueden escalar automáticamente, pero es la forma más segura.



Por ejemplo:

cuando obtiene una secuencia de comandos a través de una sesión SSH, el valor devuelto por la variable "$ 0" (cuando se utiliza la fuente ) es -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

O

#!/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
ivanleoncz
fuente
2
Votados en contra, ya que esto es simplemente incorrecto: /bin/bash -c '. ./check_source.sh'da The script WAS NOT sourced.. Mismo error: ln -s /bin/bash pumuckl; ./pumuckl -c '. ./check_source.sh'->The script WAS NOT sourced.
Tino
2
Su voto negativo ha cambiado todo el escenario e hizo una gran contribución, Tino. ¡Gracias!
ivanleoncz
0

Terminé revisando [[ $_ == "$(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

Cuando se usa curl ... | bash -s -- ARGSpara ejecutar un script remoto sobre la marcha, los $ 0 serán solo en bashlugar de normales /bin/bashcuando se ejecute el archivo de script real, por lo que uso type -p "$0"para mostrar la ruta completa de bash.

prueba:

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
osexp2003
fuente
0

Esta es una derivación de algunas otras respuestas, con respecto al soporte cruzado "universal". Esto es ciertamente muy similar a https://stackoverflow.com/a/2942183/3220983 en particular, aunque ligeramente diferente. La debilidad con esto es que un script de cliente debe respetar cómo usarlo (es decir, exportando primero una variable). La fortaleza es que esto es simple y debería funcionar "en cualquier lugar". Aquí hay una plantilla para su placer de cortar y pegar:

# 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

Nota: utilizo exportsolo asegúrese de que este mecanismo se pueda extender a subprocesos.

BuvinJ
fuente