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
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
.
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]
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
$./test2.sh
./test2.sh
./test2.sh
./test2.sh
./test.sh
$ sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh
./test.sh
$./test2.sh
./test2.sh
./test2.sh
./test2.sh
$/bin/sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh
$
$ ./test2.sh
./test.sh
./test.sh
./test.sh
$ zsh test.sh
echo
test.sh
$
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.
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 .
#!/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
./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 :
------------------------------------------
$BASH_SOURCE
$BASH_SOURCE
fonctionne en bash et uniquement en bash.$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
$0
a la même valeur que $BASH_SOURCE
en bash.$_
$_
n'est pas modifié par dash et ksh.$_
décroît jusqu'au dernier argument du dernier appel.$_
à "bash".$_
intacte. (lors de l'approvisionnement, c'est juste le résultat de la règle du "dernier argument").sh
, concernant ces tests, il se comporte comme un tiret.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
tl; dr script=$(readlink -e -- "${BASH_SOURCE}")
(pour bash évidemment)
$BASH_SOURCE
cas de testfichier 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)
$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)
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
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.
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)
}