Existe uma maneira de um script do Shell de origem descobrir o caminho para si mesmo? Estou preocupado principalmente com o bash, embora tenha alguns colegas de trabalho que usam o tcsh.
Suponho que talvez eu não tenha muita sorte aqui, pois o sourcing faz com que os comandos sejam executados no atual Shell, então $0
ainda é a invocação atual do Shell, não o script de origem. Meu melhor pensamento atualmente é fazer source $script $script
, para que o primeiro parâmetro posicional contenha as informações necessárias. Alguém tem uma maneira melhor?
Para ser claro, eu sou fonte o script, não o estou executando:
source foo.bash
Em tcsh
, $_
no início do script conterá o local se o arquivo tiver sido originado e $0
contém se foi executado.
#!/bin/tcsh
set sourced=($_)
if ("$sourced" != "") then
echo "sourced $sourced[2]"
endif
if ("$0" != "tcsh") then
echo "run $0"
endif
No Bash:
#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"
Eu acho que você poderia usar $BASH_SOURCE
variável. Retorna o caminho que foi executado:
[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
Portanto, na próxima etapa, devemos verificar se o caminho é relativo ou não. Se não for relativo, está tudo bem. Se for, podemos verificar o caminho com pwd
, concatenar com /
e $BASH_SOURCE
.
Esta solução se aplica apenas ao bash e não ao tcsh. Observe que a resposta geralmente fornecida ${BASH_SOURCE[0]}
não funcionará se você tentar encontrar o caminho de dentro de uma função.
Descobri que essa linha sempre funciona, independentemente de o arquivo estar sendo originado ou executado como um script.
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
Se você deseja seguir os links simbólicos, use readlink
no caminho acima, de forma recursiva ou não recursiva.
Aqui está um script para testá-lo e compará-lo com outras soluções propostas. Invoque-o como 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]}
}
A razão pela qual o one-liner funciona é explicada pelo uso do BASH_SOURCE
variável de ambiente e seu associado FUNCNAME
.
BASH_SOURCE
Uma variável de matriz cujos membros são os nomes de arquivos de origem em que os nomes de funções Shell correspondentes na variável de matriz FUNCNAME são definidos. A função Shell $ {FUNCNAME [$ i]} é definida no arquivo $ {BASH_SOURCE [$ i]} e é chamada a partir de $ {BASH_SOURCE [$ i + 1]}.
FUNCNAME
Uma variável de matriz que contém os nomes de todas as funções do Shell atualmente na pilha de chamadas de execução. O elemento com índice 0 é o nome de qualquer função Shell em execução no momento. O elemento mais baixo (aquele com o índice mais alto) é "principal". Essa variável existe apenas quando uma função Shell está sendo executada. As atribuições a FUNCNAME não têm efeito e retornam um status de erro. Se FUNCNAME estiver desativado, ele perderá suas propriedades especiais, mesmo que seja redefinido posteriormente.
Essa variável pode ser usada com BASH_LINENO e BASH_SOURCE. Cada elemento de FUNCNAME possui elementos correspondentes em BASH_LINENO e BASH_SOURCE para descrever a pilha de chamadas. Por exemplo, $ {FUNCNAME [$ i]} foi chamado do arquivo $ {BASH_SOURCE [$ i + 1]} na linha número $ {BASH_LINENO [$ i]}. O chamador interno exibe a pilha de chamadas atual usando essas informações.
[Fonte: Manual do Bash]
Para detalhes e interesse dos pesquisadores, aqui está o que eles fazem ... É um wiki da comunidade, portanto, fique à vontade para adicionar outros equivalentes do 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
$./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
$
Isso funcionou para mim no bash, dash, ksh e 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
Saída para esses reservatórios:
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
Tentei fazê-lo funcionar para o csh/tcsh, mas é muito difícil; Estou aderindo ao POSIX.
Fiquei um pouco confuso com a resposta do wiki da comunidade (de Shawn J. Goff), então escrevi um script para resolver as coisas. Sobre $_
, Encontrei o seguinte: so de _
como uma variável de ambiente passada para um comando . Como é uma variável de ambiente, é fácil testar seu valor incorretamente.
Abaixo está o script, então é produzido. Eles também estão em este 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;[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
./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
funciona no bash e somente no bash.$0
é quando o arquivo atual foi originado por outro arquivo. Nesse caso, $BASH_PROFILE
contém o nome do arquivo de origem, em vez do nome do arquivo de origem.$0
$0
tem o mesmo valor que $BASH_SOURCE
na festança.$_
$_
permanece intocado pelo traço e pelo ksh.$_
decai até o último argumento da última chamada.$_
agredir".$_
intocado. (ao procurar, é apenas o resultado da regra do "último argumento").sh
, em relação a esses testes, ele se comporta como traço.esta resposta descreve como lsof
e um pouco de magia grep é a única coisa que parece ter a chance de trabalhar para arquivos de origem aninhados no tcsh:
/usr/sbin/lsof +p $$ | grep -oE /.\*source_me.tcsh
tl; dr script=$(readlink -e -- "${BASH_SOURCE}")
(para bash obviamente)
$BASH_SOURCE
casos de testearquivo fornecido /tmp/source1.sh
echo '$BASH_SOURCE '"(${BASH_SOURCE})"
echo 'readlink -e $BASH_SOURCE'\
"($(readlink -e -- "${BASH_SOURCE}"))"
source
o arquivo de maneiras diferentes
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
de diferentes caminhos relativos /tmp/a
e /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
em todos os casos, se o script tiver o comando adicionado
echo '$0 '"(${0})"
então source
o script sempre é impresso
$0 (bash)
no entanto, se o script foi executado , por exemplo.
$> bash /tmp/source1.sh
então $0
seria o valor da string /tmp/source1.sh
.
$0 (/tmp/source1.sh)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)
Para o bash Shell, achei @ resposta de Dennis Williamson mais útil, mas não funcionou no caso de Sudo
. Isto faz:
if ( [[ $_ != $0 ]] && [[ $_ != $Shell ]] ); then
echo "I'm being sourced!"
exit 1
fi
Para tornar seu script compatível com bash e zsh, em vez de usar instruções if, você pode simplesmente escrever ${BASH_SOURCE[0]:-${(%):-%x}}
. O valor resultante será obtido de BASH_SOURCE[0]
Quando definido, e ${(%):-%x}}
quando BASH_SOURCE [0] não estiver definido.
A parte mais complicada é encontrar o arquivo de origem atualmente para o shell Dash usado como substituição sh no Ubuntu. O seguinte trecho de código pode ser usado no script que está sendo originado para determinar seu caminho absoluto. Testado em bash, zsh e dash invocados como dash e sh.
Nota: depende do utilitário --- realpath (1) from GNU pacote coreutils
NB: As opções lsof (1) também devem ser verificadas, porque conselhos semelhantes desta e de outras páginas não funcionaram para mim no Ubuntu 18 e 19, portanto, tive que reinventar isso.
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)
}