Cómo definir una secuencia de comandos de shell para que no se ejecute

40

Estoy definiendo un script de shell que un usuario debería en sourcelugar de ejecutar.

¿Existe una forma convencional o inteligente de indicarle al usuario que este es el caso, por ejemplo a través de una extensión de archivo?

¿Hay algún código de shell que pueda escribir en el archivo mismo, lo que hará que repita un mensaje y se cierre si se ejecuta en lugar de obtenerlo, de modo que pueda ayudar al usuario a evitar este error obvio?

alga
fuente
1
Entonces, si el usuario está escribiendo un script de shell de una línea x, que solo contiene el comando . your-script-to-be-sourced, está bien, pero si desea ejecutarlo bash your-script-to-be-sourced, ¿debería estar prohibido? ¿Cuál es el punto de esta restricción?
user1934428
8
@ user1934428 Por supuesto. Esto es normal para una secuencia de comandos que calcula una serie de envvariables y las deja como resultado de la secuencia de comandos de facto . Un novato se quedará atrapado durante días con el rompecabezas si les permite ejecutar.
kubanczyk

Respuestas:

45

Suponiendo que está ejecutando bash, coloque el siguiente código cerca del inicio del script que desea que se obtenga pero no se ejecute:

if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
    echo "Hey, you should source this script, not execute it!"
    exit 1
fi

En bash, ${BASH_SOURCE[0]}contendrá el nombre del archivo actual que el shell está leyendo independientemente de si se está obteniendo o ejecutando.

Por el contrario, $0es el nombre del archivo actual que se está ejecutando.

-efprueba si estos dos archivos son el mismo archivo. Si es así, alertamos al usuario y salimos.

Ni -eftampoco BASH_SOURCEson POSIX. Si bien -efes compatible con ksh, yash, zsh y Dash, BASH_SOURCErequiere bash. Enzsh , sin embargo, ${BASH_SOURCE[0]}podría ser reemplazado por ${(%):-%N}.

John1024
fuente
2
Simplemente echo "Usage: source \"$myfile\""
kubanczyk
66
@kubanczyk sourceno es portátil. Teniendo en cuenta que esta respuesta es específica de bash, no es tan mala, pero es un buen hábito usar el portátil.
gronostaj
33

Un archivo no ejecutable puede obtenerse pero no ejecutarse, por lo que, como primera línea de defensa, no establecer el indicador ejecutable debería ser una buena pista ...

Editar: truco con el que me topé: hacer que el shebang sea cualquier ejecutable que no sea un intérprete de shell, /bin/falsehace que el script devuelva un error (rc! = 0)

#!/bin/false "This script should be sourced in a shell, not executed directly"
xenoide
fuente
77
Sin embargo, un archivo no ejecutable todavía se puede ejecutar a través de, por ejemplo bash somefile.sh...
twalberg
1
Si sabe que puede ejecutarlo con bash(vd perl, python, awk ...), entonces ha buscado la fuente y ha visto el comentario que dice no hacerlo :)
xenoid
1
Scripts de Perl se nombran generalmente somefile.ply Python como somefile.py, lo que no, probablemente no he leído los comentarios (que son los que, incluso, de todos modos?) Y bash somefile.shes más corto de lo que escribir chmod +x somefile.sh; ./somefile.sh...
twalberg
Además, algunos shells similares a Bourne, incluido bash, primero intentarán execveun archivo, pero si eso falla, inspeccionan el archivo manualmente y lo interpretan manualmente #!e invocan a través de ese intérprete: este es un legado de los días en que #!era un espacio puramente de usuario convención, en lugar de ser manejado por el núcleo mismo. Creo que bash , al menos, no hará esto para archivos no ejecutables, pero no sé si es portátil esperar un comportamiento tan sensato de todos los shells de los que el usuario podría invocar el script.
mtraceur
"Si sabe que puede ejecutarlo con bash" Um, no, a veces el usuario simplemente no tiene idea de que existen otros shells bash que bash script.shpueden ser peligrosos.
Sergiy Kolodyazhnyy
10

Hay varios métodos sugeridos en esta publicación de Stack Overflow , de los cuales, me gustó el basado en funciones sugerido por Wirawan Purwanto y mr.spuratic mejor:

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

Para que pueda agregar al inicio del script:

function check()
{
    if [[ ${FUNCNAME[-1]} != "source" ]]   # bash 4.2+, use ${FUNCNAME[@]: -1} for older
    then
        printf "Usage: source %s\n" "$0"
        exit 1
    fi
}
check
muru
fuente
7

Asumiendo que es inútil, en lugar de perjudicial, ejecutar el script, puede agregar

return 0 || printf 'Must be sourced, not executed\n' >&2

hasta el final del guión. returnfuera de una función tiene un código de salida distinto de cero a menos que el archivo se obtenga.

chepner
fuente
3
Tenga en cuenta que esto devuelve un estado de salida 0. Prueba este idioma similar que uso en su lugar:return 2>/dev/null; echo "$0: This script must be sourced" 1>&2; exit 1
wjandrea
5

Cuando obtiene un script de shell, se ignora la línea shebang . Al colocar un shebang no válido, puede alertar al usuario de que el script se ejecutó erróneamente:

#!/bin/bash source-this-script
# ...

El mensaje de error será este:

/bin/bash: source-this-script: No such file or directory

El nombre del argumento (arbitrario) ya proporciona una pista fuerte, pero el mensaje de error aún no es 100% claro. Podemos arreglar esto con un script de utilidad source-this-scriptque se coloca en algún lugar de su PATH:

#!/bin/sh
echo >&2 "This script must be sourced, not executed${1:+: }${1:-!}"
exit 1

Ahora, el mensaje de error será este:

This script must be sourced, not executed: path/to/script.sh

Comparación con otros enfoques

En comparación con las otras respuestas, este enfoque solo requiere cambios mínimos en cada script (y tener una línea shebang ayuda con la detección del tipo de archivo en los editores y especifica el dialecto del script de shell, por lo que incluso hay beneficios). La desventaja es un mensaje de error algo poco claro, o la adición (única) de otro script de shell.

Sin embargo, no evita la invocación explícita a través de bash path/to/script.sh(¡gracias @muru!).

Ingo Karkat
fuente
1
Otro inconveniente es que esto no protegerá bash some/script.sh, lo que también ignoraría el shebang.
muru
44
Puede hacer que el mensaje sea más claro haciendo el shebang #!/bin/echo 'You must source this script!'o algo así.
Chris
1
@ Chris: Correcto, pero luego perdería la detección del tipo de archivo (por ejemplo, en Vim) y la documentación de qué dialecto de shell es este. ¡Si no te importan estos, tu sugerencia eliminaría el segundo script!
Ingo Karkat