determinar la ruta al script de shell de origen

80

¿Hay alguna manera para que un script de shell de origen descubra el camino hacia sí mismo? Me preocupa principalmente bash, aunque tengo algunos compañeros de trabajo que usan tcsh.

Supongo que es posible que no tenga mucha suerte aquí, ya que el abastecimiento hace que los comandos se ejecuten en el shell actual, por $0lo que sigue siendo la invocación del shell actual, no el script de origen. Mi mejor pensamiento actualmente es hacerlo source $script $script, para que el primer parámetro posicional contenga la información necesaria. Alguien tiene una mejor manera?

Para ser claros, estoy buscando el script, no ejecutándolo:

source foo.bash
Cascabel
fuente
pregunta relacionada que tiene más de 4200 votos a favor: stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Respuestas:

65

En tcsh, $_al comienzo de la secuencia de comandos contendrá la ubicación si el archivo se obtuvo y lo $0contiene si se ejecutó.

#!/bin/tcsh
set sourced=($_)
if ("$sourced" != "") then
    echo "sourced $sourced[2]"
endif
if ("$0" != "tcsh") then
    echo "run $0"
endif

En Bash:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"
Dennis Williamson
fuente
Acabo de tener la oportunidad de usar esto en tcsh, y noté que no funciona sin el shebang. Parece un poco extraño para el comportamiento para cambiar si sólo se compra de componentes, no ejecutarlo ...
Cascabel
La versión tcsh tampoco parece funcionar si el script se obtiene de forma no interactiva (por ejemplo, desde un cshrc). Parece que no puedo encontrar una manera de obtener la información en ese caso. ¿Alguna idea?
Cascabel
El abastecimiento funciona para mí sin el shebang. > tcsh --version\n tcsh 6.14.00 (Astron) 2005-03-25 (i486-intel-linux) options wide,nls,dl,al,kan,rh,nd,color,filec. En cuanto a la obtención de fuentes de manera no interactiva, el archivo fuente se incluye en el archivo principal como si realmente fuera parte de él (indistintamente) como usted menciona en su pregunta original. Creo que su solución de parámetros posicionales es probablemente el mejor enfoque. Sin embargo, la pregunta habitual es "¿por qué quieres hacer eso?" Y la respuesta habitual a la respuesta es "no hagas eso, haz esto en su lugar", donde "esto" suele almacenarse ...
Dennis Williamson
2
@clacke: Me parece que en todas las versiones de Bash que he probado desde 2.05b a 4.2.37, incluyendo 4.1.9, que .e sourcetrabajado de forma idéntica en este sentido. Tenga en cuenta que $_debe accederse en la primera instrucción del archivo; de lo contrario, contendrá el último argumento del comando anterior. Me gusta incluir el shebang para mi propia referencia, así que sé para qué shell debe ser y para el editor, por lo que utiliza el resaltado de sintaxis.
Dennis Williamson
1
Jaja. Obviamente estaba probando haciendo primero source, luego haciendo .. Pido disculpas por ser incompetente. De hecho son idénticos. De todos modos, $BASH_SOURCEfunciona.
clacke
30

Creo que podrías usar $BASH_SOURCEvariable. Devuelve la ruta que se ejecutó:

pbm@tauri ~ $ /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ ./a.sh
./a.sh
pbm@tauri ~ $ source /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ source ./a.sh
./a.sh

Entonces, en el siguiente paso, debemos verificar si la ruta es relativa o no. Si no es relativo, todo está bien. Si es así, podríamos verificar la ruta con pwd, concatenar con /y $BASH_SOURCE.

pbm
fuente
2
Y tenga sourceen cuenta que busca $PATHsi el nombre de pila no contiene un /. El orden de búsqueda depende de las opciones de shell, consulte el manual para más detalles.
Gilles
1
Entonces, ¿algo así mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"funcionaría?
Kevin Cantu
Gracias, una respuesta rápida y útil. Dennis gana la marca de verificación verde por dar una respuesta tcsh también. @Gilles: Correcto, lo encontré en la documentación. Afortunadamente para mi caso de uso, casi seguramente no tengo que preocuparme por eso.
Cascabel
18

Por rigor y por el bien de los buscadores, esto es lo que hacen ... Es un wiki de la comunidad, así que siéntase libre de agregar otros equivalentes de shell (obviamente, $ BASH_SOURCE será diferente).

test.sh:

#! /bin/sh
called=$_
echo $called
echo $_
echo $0
echo $BASH_SOURCE

test2.sh:

#! /bin/sh
source ./test.sh

Golpetazo:

$./test2.sh
./test2.sh
./test2.sh
./test2.sh
./test.sh
$ sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh
./test.sh

Guión, correr precipitadamente, precipitarse, ir de prisa

$./test2.sh
./test2.sh
./test2.sh
./test2.sh

$/bin/sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh

$

Zsh

$ ./test2.sh
./test.sh
./test.sh
./test.sh

$ zsh test.sh

echo
test.sh

$
Shawn J. Goff
fuente
1
No entiendo: ¿por qué called=$_; echo $called; echo $_? ¿No se imprimirá esto $_dos veces?
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
55
@CiroSantilli: No siempre, lea el manual de Bash sobre el $_parámetro especial: "Al iniciar el shell, establezca el nombre de ruta absoluto utilizado para invocar el shell o el script de shell que se ejecuta como se pasa en el entorno o la lista de argumentos. Posteriormente, se expande hasta el último argumento para el comando anterior, después de la expansión. También se establece en el nombre de ruta completo utilizado para invocar cada comando ejecutado y colocado en el entorno exportado a ese comando. Al verificar el correo, este parámetro contiene el nombre del archivo de correo ".
Adam Rosenfield
El problema con esto es que el archivo de origen tiene un encabezado #! /bin/shque lo hace inútil para la fuente. Eso comenzaría una nueva instancia de /bin/sh, establecer variables, luego salir de esa instancia, dejando la instancia de llamada sin cambios.
JamesThomasMoon1979
2
@ JamesThomasMoon1979: ¿De qué estás hablando? Cualquier cosa que comience #en un script de shell es un comentario.  #!(shebang) tiene su significado especial solo como la primera línea de un script que se ejecuta.   Como la primera línea de un archivo que se obtiene, es solo un comentario.
Scott,
17

Esta solución se aplica solo a bash y no a tcsh. Tenga en cuenta que la respuesta comúnmente proporcionada ${BASH_SOURCE[0]}no funcionará si intenta encontrar la ruta desde una función.

He encontrado que esta línea siempre funciona, independientemente de si el archivo se obtiene o se ejecuta como un script.

echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

Si desea seguir el uso de enlaces simbólicos readlinken la ruta que obtiene arriba, recursivamente o no recursivamente.

Aquí hay un script para probarlo y compararlo con otras soluciones propuestas. Invocarlo como source test1/test2/test_script.sho bash test1/test2/test_script.sh.

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

La razón por la que funciona One-liner se explica por el uso de la BASH_SOURCEvariable de entorno y su asociado FUNCNAME.

BASH_SOURCE

Una variable de matriz cuyos miembros son los nombres de los archivos de origen donde se definen los nombres de función de shell correspondientes en la variable de matriz FUNCNAME. La función de shell $ {FUNCNAME [$ i]} se define en el archivo $ {BASH_SOURCE [$ i]} y se llama desde $ {BASH_SOURCE [$ i + 1]}.

FUNCNAME

Una variable de matriz que contiene los nombres de todas las funciones de shell actualmente en la pila de llamadas de ejecución. El elemento con índice 0 es el nombre de cualquier función de shell que se esté ejecutando actualmente. El elemento más inferior (el que tiene el índice más alto) es "principal". Esta variable existe solo cuando se ejecuta una función de shell. Las asignaciones a FUNCNAME no tienen efecto y devuelven un estado de error. Si FUNCNAME no está configurado, pierde sus propiedades especiales, incluso si posteriormente se restablece.

Esta variable se puede usar con BASH_LINENO y BASH_SOURCE. Cada elemento de FUNCNAME tiene elementos correspondientes en BASH_LINENO y BASH_SOURCE para describir la pila de llamadas. Por ejemplo, $ {FUNCNAME [$ i]} se llamó desde el archivo $ {BASH_SOURCE [$ i + 1]} en el número de línea $ {BASH_LINENO [$ i]}. La persona que llama muestra la pila de llamadas actual utilizando esta información.

[Fuente: manual de Bash]

gkb0986
fuente
Esta solución funcionó para mí en bash, mientras que la respuesta seleccionada funcionó solo de manera intermitente. Nunca entendí por qué funcionaba a veces y no a otros (tal vez no estaba prestando suficiente atención al shell de abastecimiento).
Jim2B
13

Esto funcionó para mí en bash, dash, ksh y zsh:

if test -n "$BASH" ; then script=$BASH_SOURCE
elif test -n "$TMOUT"; then script=${.sh.file}
elif test -n "$ZSH_NAME" ; then script=${(%):-%x}
elif test ${0##*/} = dash; then x=$(lsof -p $$ -Fn0 | tail -1); script=${x#n}
else script=$0
fi

echo $script

Salida para estos proyectiles:

BASH source: ./myscript
ZSH source: ./myscript
KSH source: /home/pbrannan/git/theme/src/theme/web/myscript
DASH source: /home/pbrannan/git/theme/src/theme/web/myscript
BASH: ./myscript
ZSH: ./myscript
KSH: /home/pbrannan/git/theme/src/theme/web/myscript
DASH: ./myscript

Traté de hacerlo funcionar para csh / tcsh, pero es demasiado difícil; Me estoy quedando con POSIX.

Paul Brannan
fuente
1

Estaba un poco confundido por la respuesta wiki de la comunidad (de Shawn J. Goff), así que escribí un guión para resolver las cosas. Acerca de $_, encontré esto: Uso de _como una variable de entorno pasada a un comando . Es una variable de entorno, por lo que es fácil probar su valor incorrectamente.

A continuación se muestra el script, luego es salida. También están en esta esencia .

test-shell-default-variables.sh

#!/bin/bash

# test-shell-default-variables.sh

# Usage examples (you might want to `sudo apt install zsh ksh`):
#
#  ./test-shell-default-variables.sh dash bash
#  ./test-shell-default-variables.sh dash bash zsh ksh
#  ./test-shell-default-variables.sh dash bash zsh ksh | less -R

# `-R` in `less -R` to have less pass escape sequences directly to the terminal
# so we have colors.


# The "invoking with name `sh`" tests are commented because for every shell I
# tested (dash, bash, zsh and ksh), the output was the same as that of dash.

# The `test_expression` function also work with expansion changes. You can try
# lines like `test_expression '{BASH_SOURCE:-$0}'`.

echolor() {
    echo -e "\e[1;36m$@\e[0m"
}

tell_file() {
    echo File \`"$1"\` is:
    echo \`\`\`
    cat "$1"
    echo \`\`\`
    echo
}

SHELL_ARRAY=("$@")

test_command() {
    for shell in "${SHELL_ARRAY[@]}"
    do
        prepare "$shell"
        cmd="$(eval echo $1)"
        # echo "cmd: $cmd"
        printf '%-4s: ' "$shell"
        { env -i $cmd 2>&1 1>&3 | sed 's/^/[err]/'; } 3>&1
        teardown
    done
    echo
}

prepare () {
    shell="$1"
    PATH="$PWD/$shell/sh:$PATH"
}

teardown() {
    PATH="${PATH#*:}"
}


###
### prepare
###
for shell in "${SHELL_ARRAY[@]}"
do
    mkdir "$shell"
    ln -sT "/bin/$shell" "$shell/sh"
done

echo > printer.sh
echo '. ./printer.sh' > sourcer.sh
rm linked.sh &>/dev/null; ln -sT "printer.sh" "linked.sh"

tell_file sourcer.sh

###
### run
###
test_expression() {
    local expr="$1"

    # prepare
    echo "echo $expr" > printer.sh
    tell_file printer.sh

    # run
    cmd='$shell ./printer.sh'
    echolor "\`$cmd\` (simple invocation) ($expr):"
    test_command "$cmd"

    # cmd='sh ./printer.sh'
    # echolor "\`$cmd\` (when executable name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./sourcer.sh'
    echolor "\`$cmd\` (via sourcing) ($expr):"
    test_command "$cmd"

    # cmd='sh ./sourcer.sh'
    # echolor "\`$cmd\` (via sourcing, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./linked.sh'
    echolor "\`$cmd\` (via symlink) ($expr):"
    test_command "$cmd"

    # cmd='sh ./linked.sh'
    # echolor "\`$cmd\` (via symlink, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    echolor "------------------------------------------"
    echo
}

test_expression '$BASH_SOURCE'
test_expression '$0'
test_expression '$(/bin/true x y; true a b c; echo $_)' # Rq: true is a builtin
test_expression '$_'

###
### teardown
###
for shell in "${SHELL_ARRAY[@]}"
do
    rm "$shell/sh"
    rm -d "$shell"
done

rm sourcer.sh
rm linked.sh
rm printer.sh

Salida de ./test-shell-default-variables.sh {da,ba,z,k}sh

File `sourcer.sh` is:
```
. ./printer.sh
```

File `printer.sh` is:
```
echo $BASH_SOURCE
```

`$shell ./printer.sh` (simple invocation) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./linked.sh` (via symlink) ($BASH_SOURCE):
dash: 
bash: ./linked.sh
zsh : 
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $0
```

`$shell ./printer.sh` (simple invocation) ($0):
dash: ./printer.sh
bash: ./printer.sh
zsh : ./printer.sh
ksh : ./printer.sh

`$shell ./sourcer.sh` (via sourcing) ($0):
dash: ./sourcer.sh
bash: ./sourcer.sh
zsh : ./printer.sh
ksh : ./sourcer.sh

`$shell ./linked.sh` (via symlink) ($0):
dash: ./linked.sh
bash: ./linked.sh
zsh : ./linked.sh
ksh : ./linked.sh

------------------------------------------

File `printer.sh` is:
```
echo $(/bin/true x y; true a b c; echo $_)
```

`$shell ./printer.sh` (simple invocation) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./linked.sh` (via symlink) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $_
```

`$shell ./printer.sh` (simple invocation) ($_):
dash: 
bash: bash
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($_):
dash: 
bash: bash
zsh : ./printer.sh
ksh : 

`$shell ./linked.sh` (via symlink) ($_):
dash: 
bash: bash
zsh : 
ksh : 

------------------------------------------

Que aprendimos

$BASH_SOURCE

  • $BASH_SOURCE funciona en bash y solo en bash.
  • La única diferencia con $0es cuando el archivo actual fue originado por otro archivo. En ese caso, $BASH_PROFILEcontiene el nombre del archivo de origen, en lugar del nombre del archivo de origen.

$0

  • En zsh, $0tiene el mismo valor que $BASH_SOURCEen bash.

$_

  • $_ se deja sin tocar por guión y ksh.
  • En bash y zsh, $_decae al último argumento de la última llamada.
  • bash se inicializa $_a "bash".
  • zsh deja $_sin tocar. (cuando se busca, es solo el resultado de la regla del "último argumento").

Enlaces simbólicos

  • Cuando se llama a un script a través de un enlace simbólico, ninguna variable contiene ninguna referencia al destino del enlace, solo su nombre.

ksh

  • Con respecto a esas pruebas, ksh se comporta como un guión.

sh

  • Cuando se llama a bash o zsh a través de un enlace simbólico llamado sh, con respecto a esas pruebas, se comporta como un guión.
Mathieu CAROFF
fuente
0

Para el bash shell, encontré la respuesta de @Dennis Williamson más útil, pero no funcionó en el caso de sudo. Esto hace:

if ( [[ $_ != $0 ]] && [[ $_ != $SHELL ]] ); then
    echo "I'm being sourced!"
    exit 1
fi
Mate
fuente
0

Para hacer que su script sea compatible con bash y zsh en lugar de usar sentencias if, simplemente puede escribir ${BASH_SOURCE[0]:-${(%):-%x}}. El valor resultante se tomará de BASH_SOURCE[0]cuando está definido y ${(%):-%x}}cuando BASH_SOURCE [0] no está definido.

dols3m
fuente
0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (para bash obviamente)


$BASH_SOURCE Casos de prueba

archivo dado /tmp/source1.sh

echo '$BASH_SOURCE '"(${BASH_SOURCE})"
echo 'readlink -e $BASH_SOURCE'\
     "($(readlink -e -- "${BASH_SOURCE}"))"

source el archivo de diferentes maneras

source de /tmp

$> cd /tmp

$> source source1.sh
$BASH_SOURCE (source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source ./source1.sh
$BASH_SOURCE (./source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source /tmp/source1.sh
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

source de /

cd /
$> source /tmp/source1.sh
$0 (bash)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

sourcede diferentes caminos relativos /tmp/ay/var

$> cd /tmp/a

$> source ../source1.sh
$BASH_SOURCE (../source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> cd /var

$> source ../tmp/source1.sh
$BASH_SOURCE (../tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

respecto a $0

en todos los casos, si el script tenía el comando agregado

echo '$0 '"(${0})"

entonces sourceel guión siempre se imprime

$0 (bash)

sin embargo , si se ejecutó el script , por ej.

$> bash /tmp/source1.sh

entonces $0sería un valor de cadena /tmp/source1.sh.

$0 (/tmp/source1.sh)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)
JamesThomasMoon1979
fuente
0

esta respuesta describe cómo lsofy un poco de magia grep es lo único que parece tener posibilidades de funcionar para archivos de origen anidados en tcsh:

/usr/sbin/lsof +p $$ | grep -oE /.\*source_me.tcsh
Patrick Maupin
fuente
-2
wdir="$PWD"; [ "$PWD" = "/" ] && wdir=""
case "$0" in
  /*) scriptdir="${0%/*}";;
  *) scriptdir="$wdir/${0#./}"; scriptdir="${scriptdir%/*}";;
esac
echo "$scriptdir"

Tal vez esto no funcione con enlaces simbólicos o archivos de origen, pero funcionará para archivos normales. Tomado como referencia para. @kenorb Sin nombre de directorio, enlace de lectura, BASH_SOURCE.

HemanthJabalpuri
fuente
1
Se explicó en la pregunta que le $0brinda información sobre el script que se está ejecutando actualmente , no uno de origen.
Scott
-3

En realidad, "dirname $ 0" te dará la ruta al script, pero debes interpretarlo un poco:

$ cat bash0
#!/bin/bash
echo \$0=$0
dirname $0
$ bash0    # "." appears in PATH right now.
$0=./bash0
.
$ ./bash0
$0=./bash0
.
$ $PWD/bash0
$0=/home/00/bediger/src/ksh/bash0
/home/00/bediger/src/ksh
$ $PWD/../ksh/bash0
$0=/home/00/bediger/src/ksh/../ksh/bash0
/home/00/bediger/src/ksh/../ksh
$ ../ksh/bash0
$0=../ksh/bash0
../ksh

Tienes que prepararte para manejar "." como el nombre del directorio en algunas circunstancias comunes. Experimentaría un poco, ya que recuerdo el nombre de directorio incorporado en ksh haciendo las cosas de manera un poco diferente cuando "." aparece en RUTA.

Bruce Ediger
fuente
44
Este es un script de origen, no un script ejecutado. $0simplemente contiene "bash" para un shell interactivo, y eso es todo lo que ve el script de origen.
Cascabel