détermination du chemin d'accès au script shell

80

Existe-t-il un moyen pour un script shell source de connaître le chemin d'accès à lui-même? Je suis principalement concerné par bash, bien que certains de mes collègues utilisent tcsh.

Je suppose que je n'aurai peut-être pas beaucoup de chance ici, étant donné que le sourcing entraîne l'exécution des commandes dans le shell actuel. Il en va donc de $0même que l'invocation du shell actuel et non le script sourced. Ma meilleure pensée est actuellement de faire source $script $script, de sorte que le premier paramètre de position contienne les informations nécessaires. Quelqu'un a un meilleur moyen?

Pour être clair, je cherche le script, pas l'exécute:

source foo.bash
Cascabel
la source
question liée qui a plus de 4200 votes positifs: stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Réponses:

65

En tcsh, $_au début du script contiendra l'emplacement si le fichier a été sourced et $0contient si elle a été exécutée.

#!/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"
Dennis Williamson
la source
J'ai juste eu l'occasion de l'utiliser dans tcsh et j'ai remarqué que cela ne fonctionnait pas sans le shebang. Cela semble un peu étrange que le comportement change si vous le recherchez, mais ne l'exécutez pas ...
Cascabel
La version de tcsh ne semble pas non plus fonctionner si le script provient de manière non interactive (par exemple, à partir d'un cshrc). Je n'arrive pas à trouver un moyen d'obtenir l'information dans ce cas. Des pensées?
Cascabel,
Sourcing cela fonctionne pour moi sans 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 ce qui concerne la recherche de manière non interactive, le fichier source est inclus dans le fichier parent comme s'il en faisait réellement partie (indiscernablement) comme vous l'avez mentionné dans votre question d'origine. Je pense que votre solution de contournement des paramètres de position est probablement la meilleure approche. Cependant, la question habituelle est "pourquoi voulez-vous faire cela" et la réponse habituelle à la réponse est "ne faites pas cela - faites ceci à la place" où "ceci" est souvent à stocker ...
Dennis Williamson
2
@clacke: Je trouve que dans toutes les versions de Bash que j'ai testé de 2.05b à 4.2.37, y compris 4.1.9, qui .et sourcetravaillé de manière identique à cet égard. Notez que vous $_devez accéder à la première instruction du fichier, sinon elle contiendra le dernier argument de la commande précédente. J'aime inclure le shebang pour ma propre référence afin que je sache quel shell il est supposé être et pour l'éditeur, il utilise donc la coloration syntaxique.
Dennis Williamson
1
Haha. Évidemment, je testais d'abord source, puis ensuite .. Je m'excuse d'être incompétent. Ils sont en effet identiques. En tout cas, ça $BASH_SOURCEmarche.
Clacke
30

Je pense que vous pourriez utiliser $BASH_SOURCEvariable. Il retourne 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 devrions 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.

pbm
la source
2
Et notez que sourcerecherche $PATHsi le nom donné ne contient pas de /. L'ordre de recherche dépend des options du shell. Reportez-vous au manuel pour plus de détails.
Gilles
1
Alors, quelque chose comme ça mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"marcherait?
Kevin Cantu
Merci, une réponse rapide et utile. Dennis remporte la coche verte pour avoir également répondu à tcsh. @ Gilles: D'accord, j'ai trouvé cela dans la documentation. Heureusement pour mon cas d'utilisation, je n'ai presque certainement pas à m'en soucier.
Cascabel
18

Par souci de rigueur et de souci des chercheurs, voici ce qu'ils font ... C'est un wiki de communauté, alors n'hésitez pas à ajouter les équivalents des autres 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

$
Shawn J. Goff
la source
1
Je ne comprend pas: pourquoi called=$_; echo $called; echo $_? Cette impression ne sera-t-elle pas $_deux fois?
Ciro Santilli a annoncé le
5
@CiroSantilli: Pas toujours, lisez le manuel de Bash à propos du $_paramètre spécial: "Au démarrage du shell, définissez le chemin absolu utilisé pour appeler le shell ou le script de shell exécuté, comme dans la liste d'environnement ou d'arguments. argument de la commande précédente, après le développement. Définit également le chemin complet utilisé pour appeler chaque commande exécutée et placée dans l'environnement exporté vers cette commande. Lors de la vérification du courrier, ce paramètre contient le nom du fichier courrier. "
Adam Rosenfield
Le problème avec ceci est que le fichier source a un en-tête #! /bin/shqui le rend inutile à la source. Cela créerait une nouvelle instance de /bin/sh, définir des variables, puis quitterait cette instance, en laissant l'instance appelante inchangée.
JamesThomasMoon1979
2
@ JamesThomasMoon1979: De quoi parlez-vous? Tout ce qui commence par #un script shell est un commentaire.  #!(shebang) n’a sa signification particulière que comme première ligne d’un script exécuté.   En tant que première ligne d'un fichier source, il ne s'agit que d'un commentaire.
Scott
17

Cette solution s'applique uniquement à bash et non à tcsh. Notez que la réponse généralement 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 recherché ou exécuté en tant que script.

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

Si vous souhaitez suivre les liens symboliques utilisés readlinksur le chemin que vous obtenez ci-dessus, de manière récursive ou non récursive.

Voici un script pour l'essayer et le comparer à d'autres solutions proposées. Invoquez-le comme source test1/test2/test_script.shou 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 one-liner fonctionne est expliquée par l'utilisation de la BASH_SOURCEvariable d'environnement et de son associé FUNCNAME.

BASH_SOURCE

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

FUNCNAME

Une variable de tableau contenant les noms de toutes les fonctions du shell actuellement dans la pile d'appels d'exécution. L'élément d'indice 0 est le nom de toute fonction shell en cours d'exécution. L'élément le plus bas (celui avec l'indice le plus élevé) est "principal". Cette variable n'existe que lorsqu'une fonction shell est en cours d'exécution. Les assignations à FUNCNAME n'ont aucun effet et renvoient un statut 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]}. L'appelant intégré affiche la pile d'appels en cours en utilisant ces informations.

[Source: Manuel Bash]

gkb0986
la source
Cette solution a fonctionné pour moi en bash alors que la réponse sélectionnée ne fonctionnait que par intermittence. Je n’ai jamais compris pourquoi cela fonctionnait parfois et pas d’autres (peut-être que je n’étais pas assez attentif au shell d’approvisionnement).
Jim2B
13

Cela a fonctionné pour moi en 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 difficile; Je m'en tiens à POSIX.

Paul Brannan
la source
1

La réponse du wiki de la communauté (de Shawn J. Goff) me confondait un peu, alors j'ai écrit un script pour arranger les choses. À propos de $_, j’ai trouvé ceci: Utilisation de _comme variable d’environnement transmise à une commande . C'est une variable d'environnement, il est donc facile de tester sa valeur de manière incorrecte.

Vous trouverez ci-dessous le script, puis sa sortie. Ils sont aussi dans cette essence .

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 à bash et seulement à bash.
  • La seule différence avec $0est que le fichier actuel a été recherché par un autre fichier. Dans ce cas, $BASH_PROFILEcontient le nom du fichier source plutôt que celui du fichier source.

$0

  • Dans zsh, $0a la même valeur que $BASH_SOURCEdans bash.

$_

  • $_ est laissé intact par dash et ksh.
  • En bash et zsh, $_décroît jusqu'au dernier argument du dernier appel.
  • bash s'initialise $_sur "bash".
  • zsh laisse $_intacte. (lors du sourcing, 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, mais uniquement son nom.

ksh

  • En ce qui concerne ces tests, ksh se comporte comme un tiret.

sh

  • Lorsque bash ou zsh est appelé via un lien symbolique nommé sh, en ce qui concerne ces tests, il se comporte comme un tiret.
Mathieu CAROFF
la source
0

Pour rendre votre script compatible avec bash et zsh au lieu d'utiliser des instructions if, vous pouvez simplement écrire ${BASH_SOURCE[0]:-${(%):-%x}}. La valeur résultante sera prise à partir de BASH_SOURCE[0]sa définition et de celle de ${(%):-%x}}BASH_SOURCE [0].

dols3m
la source
0

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)

sourcede différents chemins relatifs /tmp/aet/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})"

alors sourcele script toujours imprimé

$0 (bash)

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

$> bash /tmp/source1.sh

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

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

cette réponse décrit comment lsofet un peu de magie grep est la seule chose qui semble offrir une chance de travail pour les fichiers à source imbriquée sous tcsh:

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

Peut-être que cela ne fonctionnera pas avec des liens symboliques ou des fichiers sources mais fonctionnera pour des fichiers normaux. Pris comme référence @kenorb Non dirname, readlink, BASH_SOURCE.

HemanthJabalpuri
la source
1
Cela a été expliqué dans la question qui $0vous donne des informations sur le script en cours d'exécution , et non sur un script source.
Scott
-3

En fait, "dirname $ 0" vous donnera le chemin du script, mais vous devez l'interpréter un peu:

$ 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

Vous devez vous préparer à gérer "." comme nom de répertoire dans certaines circonstances courantes. J'essayais un peu, car je me souviens du nom de répertoire intégré à ksh qui fait les choses un peu différemment quand "." apparaît dans PATH.

Bruce Ediger
la source
4
Ceci est un script source, pas un script exécuté. $0contient simplement "bash" pour un shell interactif, et c'est tout ce que le script source voit.
Cascabel