it-swarm-es.tech

determinar la ruta al script Shell de origen

¿Hay alguna manera de que un script fuente Shell encuentre la ruta 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, así que $0 sigue siendo la invocación actual de Shell, no el script de origen. Mi mejor pensamiento actualmente es hacer source $script $script, para que el primer parámetro posicional contenga la información necesaria. Alguien tiene una mejor manera?

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

source foo.bash
86
Cascabel

En tcsh, $_ al comienzo de la secuencia de comandos contendrá la ubicación si el archivo se obtuvo y $0 lo contiene 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"

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

[email protected] ~ $ /home/pbm/a.sh 
/home/pbm/a.sh
[email protected] ~ $ ./a.sh
./a.sh
[email protected] ~ $ source /home/pbm/a.sh 
/home/pbm/a.sh
[email protected] ~ $ 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.

32
pbm

Esta solución se aplica solo a bash y no a tcsh. Tenga en cuenta que la respuesta comúnmente suministrada ${BASH_SOURCE[0]} no funcionará si intentas 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 enlaces simbólicos, use readlink en 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.sh o 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 BASH_SOURCE variable 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 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 solo existe cuando se ejecuta una función 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 se restablece posteriormente.

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]

21
gkb0986

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

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

$
18
Shawn J. Goff

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 pegando a POSIX.

16
Paul Brannan

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: so de _ como 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 se emite. También están en este Gist .

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;[email protected]\e[0m"
}

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

Shell_ARRAY=("[email protected]")

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 $0 es cuando el archivo actual fue originado por otro archivo. En ese caso, $BASH_PROFILE contiene el nombre del archivo de origen, en lugar del nombre del archivo de origen.

$0

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

$_

  • $_ se deja sin tocar por guión y ksh.
  • En bash y zsh, $_ decae al último argumento de la última llamada.
  • bash 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.
2
Mathieu CAROFF

esta respuesta describe cómo lsof y un poco de grep magic 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
0
Patrick Maupin

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)

source desde diferentes rutas relativas /tmp/a y /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)

con respecto a $0

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

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

entonces source el script siempre se imprime

$0 (bash)

sin embargo, si el script se ejecutó , p.

$> bash /tmp/source1.sh

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

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

Para bash Shell, encontré @ 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
0
Matt

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.

0
dols3m

La parte más complicada es encontrar el archivo de origen actual para el tablero Shell utilizado como reemplazo de sh en Ubuntu. El siguiente fragmento de código se puede utilizar en el script que se obtiene para determinar su ruta absoluta. Probado en bash, zsh y dash invocado tanto como dash y sh.

NB: depende de la utilidad moderna realpath (1) de GNU paquete coreutils

Nota: las opciones lsof (1) también deben verificarse porque los consejos similares de esta y otras páginas no me funcionaron en Ubuntu 18 y 19, por lo que tuve que reinventar esto.

getShellName() {
    [ -n "$BASH" ] && echo ${BASH##/*/} && return
    [ -n "$ZSH_NAME" ] && echo $ZSH_NAME && return
    echo ${0##/*/}
}

getCurrentScript() {
    local result
    case "$(getShellName)" in
        bash )  result=${BASH_SOURCE[0]}
                ;;
        zsh )   emulate -L zsh
                result=${funcfiletrace[1]%:*}
                ;;
        dash | sh )
                result=$(
                    lsof -p $$ -Fn  \
                    | tail --lines=1  \
                    | xargs --max-args=2  \
                    | cut --delimiter=' ' --fields=2
                )
                result=${result#n}
                ;;
        * )     result=$0
                ;;
    esac
    echo $(realpath $result)
}
0
maoizm