web-dev-qa-db-fra.com

détermination du chemin vers le script Shell source

Existe-t-il un moyen pour un script Shell source de trouver le chemin vers lui-même? Je suis principalement préoccupé par bash, même si j'ai des collègues qui utilisent tcsh.

Je suppose que je n'ai peut-être pas beaucoup de chance ici, car l'approvisionnement entraîne l'exécution de commandes dans le shell actuel, donc $0 est toujours l'invocation actuelle de Shell, pas le script source. Ma meilleure idée est actuellement de faire source $script $script, de sorte que le premier paramètre positionnel contienne les informations nécessaires. Quelqu'un a une meilleure façon?

Pour être clair, je suis sourcing le script, je ne l'exécute pas:

source foo.bash
86
Cascabel

Dans tcsh, $_ au début du script contiendra l'emplacement si le fichier provient et $0 le contient s'il a été exécuté.

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

Dans Bash:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"

Je pense que vous pourriez utiliser $BASH_SOURCE variable. Il renvoie le chemin qui a été exécuté:

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

Donc, à l'étape suivante, nous devons vérifier si le chemin est relatif ou non. Si ce n'est pas relatif, tout va bien. Si c'est le cas, nous pourrions vérifier le chemin avec pwd, concaténer avec / et $BASH_SOURCE.

32
pbm

Cette solution s'applique uniquement à bash et non à tcsh. Notez que la réponse communément fournie ${BASH_SOURCE[0]} ne fonctionnera pas si vous essayez de trouver le chemin depuis une fonction.

J'ai trouvé que cette ligne fonctionnait toujours, que le fichier soit d'origine ou exécuté en tant que script.

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

Si vous voulez suivre les liens symboliques, utilisez readlink sur le chemin que vous obtenez ci-dessus, récursivement ou non récursivement.

Voici un script pour l'essayer et le comparer à d'autres solutions proposées. Appelez-le comme source test1/test2/test_script.sh ou 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 raison pour laquelle le one-liner fonctionne s'explique par l'utilisation du BASH_SOURCE variable d'environnement et son associé FUNCNAME.

BASH_SOURCE

Variable de tableau dont les membres sont les noms de fichiers source où les noms de fonction Shell correspondants dans la variable de tableau FUNCNAME sont définis. La fonction Shell $ {FUNCNAME [$ i]} est définie dans le fichier $ {BASH_SOURCE [$ i]} et appelée à partir de $ {BASH_SOURCE [$ i + 1]}.

FUNCNAME

Variable de tableau contenant les noms de toutes les fonctions Shell actuellement dans la pile des appels d'exécution. L'élément avec l'index 0 est le nom de toute fonction Shell en cours d'exécution. L'élément le plus bas (celui dont l'indice est le plus élevé) est "principal". Cette variable existe uniquement lorsqu'une fonction Shell est en cours d'exécution. Les affectations à FUNCNAME n'ont aucun effet et renvoient un état d'erreur. Si FUNCNAME n'est pas défini, il perd ses propriétés spéciales, même s'il est réinitialisé par la suite.

Cette variable peut être utilisée avec BASH_LINENO et BASH_SOURCE. Chaque élément de FUNCNAME a des éléments correspondants dans BASH_LINENO et BASH_SOURCE pour décrire la pile d'appels. Par exemple, $ {FUNCNAME [$ i]} a été appelé à partir du fichier $ {BASH_SOURCE [$ i + 1]} au numéro de ligne $ {BASH_LINENO [$ i]}. La fonction intégrée de l'appelant affiche la pile d'appels actuelle à l'aide de ces informations.

[Source: manuel Bash]

21
gkb0986

Pour la rigueur et pour le bien des chercheurs, voici ce qu'ils font ... C'est un wiki communautaire, alors n'hésitez pas à ajouter d'autres équivalents de Shell (évidemment, $ BASH_SOURCE sera différent).

test.sh:

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

test2.sh:

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

Frapper:

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

Tiret

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

Cela a fonctionné pour moi dans bash, dash, ksh et 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

Sortie pour ces coquilles:

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

J'ai essayé de le faire fonctionner pour csh/tcsh, mais c'est trop dur; Je m'en tiens à POSIX.

16
Paul Brannan

J'étais un peu confus par la réponse wiki communautaire (de Shawn J. Goff), alors j'ai écrit un script pour trier les choses. À propos $_, J'ai trouvé ceci: tilisation de _ comme variable d'environnement passée à une commande . Il s'agit d'une variable d'environnement, il est donc facile de tester sa valeur incorrectement.

Ci-dessous le script, puis sa sortie. Ils sont également dans ce 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;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

Sortie 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 : 

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

Qu'avons-nous appris?

$BASH_SOURCE

  • $BASH_SOURCE fonctionne en bash et uniquement en bash.
  • La seule différence avec $0 correspond au moment où le fichier actuel provient d'un autre fichier. Dans ce cas, $BASH_PROFILE contient le nom du fichier source, plutôt que celui du fichier source.

$0

  • En zsh, $0 a la même valeur que $BASH_SOURCE en bash.

$_

  • $_ n'est pas modifié par dash et ksh.
  • En bash et zsh, $_ décroît jusqu'au dernier argument du dernier appel.
  • bash initialise $_ à "bash".
  • zsh laisse $_ intacte. (lors de l'approvisionnement, c'est juste le résultat de la règle du "dernier argument").

Liens symboliques

  • Lorsqu'un script est appelé via un lien symbolique, aucune variable ne contient de référence à la destination du lien, uniquement son nom.

ksh

  • Concernant ces tests, ksh se comporte comme dash.

sh

  • Lorsque bash ou zsh est appelé via un lien symbolique nommé sh, concernant ces tests, il se comporte comme un tiret.
2
Mathieu CAROFF

cette réponse décrit comment lsof et un peu de magie grep est la seule chose qui semble avoir une chance de fonctionner pour les fichiers d'origine imbriqués sous tcsh:

/usr/sbin/lsof +p $$ | grep -oE /.\*source_me.tcsh
0
Patrick Maupin

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (pour bash évidemment)


$BASH_SOURCE cas de test

fichier donné /tmp/source1.sh

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

source le fichier de différentes manières

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 à partir de différents chemins relatifs /tmp/a et /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)

en ce qui concerne $0

dans tous les cas, si le script avait la commande ajoutée

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

puis source le script toujours imprimé

$0 (bash)

cependant, si le script a été exécuté , par ex.

$> bash /tmp/source1.sh

puis $0 serait une valeur de chaîne /tmp/source1.sh.

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

Pour le shell bash, j'ai trouvé @ la réponse de Dennis Williamson très utile, mais cela n'a pas fonctionné dans le cas de Sudo. Cela fait:

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

Pour rendre votre script compatible avec bash et zsh au lieu d'utiliser les instructions if, vous pouvez simplement écrire ${BASH_SOURCE[0]:-${(%):-%x}}. La valeur résultante sera extraite de BASH_SOURCE[0] Lorsqu'elle est définie et de ${(%):-%x}} lorsque BASH_SOURCE [0] n'est pas défini.

0
dols3m

La partie la plus délicate consiste à trouver le fichier actuellement utilisé pour le shell Dash utilisé comme remplacement sh dans Ubuntu. L'extrait de code suivant peut être utilisé dans le script d'origine pour déterminer son chemin absolu. Testé en bash, zsh et dash invoqué à la fois comme dash et sh.

NB: dépend de l'utilitaire moderne realpath (1) de GNU paquet coreutils

NB: Les options lsof (1) doivent également être vérifiées car des conseils similaires à la fois de cette page et d'autres pages ne fonctionnaient pas pour moi sur Ubuntu 18 et 19, j'ai donc dû réinventer cela.

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