determinando o caminho para o script de shell originado

73

Existe uma maneira de um script shell sourced descobrir o caminho para si mesmo? Estou preocupado principalmente com o bash, embora eu tenha alguns colegas de trabalho que usam o tcsh.

Eu estou supondo que talvez não tenha muita sorte aqui, já que o sourcing faz com que comandos sejam executados no shell atual, então $0 ainda é a invocação do shell atual, 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 um jeito melhor?

Para deixar claro, estou fazendo o sourcing do script, sem executá-lo:

source foo.bash
    
por Cascabel 08.12.2010 / 16:02

10 respostas

61

Em tcsh , $_ no início do script conterá o local se o arquivo foi originado e $0 o contém, caso tenha sido 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"
    
por 08.12.2010 / 20:27
28

Acho que você poderia usar a variável $BASH_SOURCE . Ele retorna o caminho que foi executado:

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

Então, no próximo passo, devemos verificar se o caminho é relativo ou não. Se não é relativo tudo está bem. Se for, podemos verificar o caminho com pwd , concatenar com / e $BASH_SOURCE .

    
por 08.12.2010 / 16:52
16

Para o rigor e o bem dos pesquisadores, aqui está o que eles fazem ... É um wiki da comunidade, então sinta-se à 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

Bash:

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

Traço

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

$
    
por 06.11.2018 / 18:49
14

Esta solução aplica-se apenas ao bash e não ao tcsh. Observe que a resposta normalmente fornecida ${BASH_SOURCE[0]} não funcionará se você tentar encontrar o caminho a partir de uma função.

Eu 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ê quiser seguir os links simbólicos, use readlink no caminho que você obtém acima, de forma recursiva ou não recursiva.

Aqui está um script para experimentá-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 da variável de ambiente BASH_SOURCE e seu associado FUNCNAME .

BASH_SOURCE

An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined. The shell function ${FUNCNAME[$i]} is defined in the file ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}.

FUNCNAME

An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element (the one with the highest index) is "main". This variable exists only when a shell function is executing. Assignments to FUNCNAME have no effect and return an error status. If FUNCNAME is unset, it loses its special properties, even if it is subsequently reset.

This variable can be used with BASH_LINENO and BASH_SOURCE. Each element of FUNCNAME has corresponding elements in BASH_LINENO and BASH_SOURCE to describe the call stack. For instance, ${FUNCNAME[$i]} was called from the file ${BASH_SOURCE[$i+1]} at line number ${BASH_LINENO[$i]}. The caller builtin displays the current call stack using this information.

[Fonte: Manual de bash]

    
por 31.08.2014 / 22:58
11

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

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

Eu tentei fazer isso funcionar para csh / tcsh, mas é muito difícil; Eu estou aderindo ao POSIX.

    
por 15.03.2017 / 16:49
1

Eu estava um pouco confuso com a resposta do wiki da comunidade (de Shawn J. Goff), então escrevi um script para resolver as coisas. Sobre $_ , achei isso: Uso de _ como uma variável de ambiente passada para um comando . É uma variável de ambiente, por isso é fácil testar o valor incorretamente.

Abaixo está o script, então é a saída. Eles também estão em esta essência .

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

Saída 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 : 

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

O que aprendemos?

$BASH_SOURCE

  • $BASH_SOURCE funciona no bash e apenas no bash.
  • A única diferença com $0 é quando o arquivo atual foi originado por outro arquivo. Nesse caso, $BASH_PROFILE contém o nome do arquivo originado, em vez daquele do arquivo de acidificação.

$0

  • Em zsh, $0 tem o mesmo valor que $BASH_SOURCE no bash.

$_

  • $_ não foi alterado por traço e ksh.
  • No bash e zsh, $_ decai para o último argumento da última chamada.
  • bash inicializa $_ para "bash".
  • zsh deixa $_ intocado. (quando se procura, é apenas o resultado da regra do "último argumento").

links simbólicos

  • Quando um script é chamado por meio de um link simbólico, nenhuma variável contém referência ao destino do link, somente seu nome.

ksh

  • Com relação a esses testes, o ksh se comporta como um traço.

sh

  • Quando bash ou zsh é chamado por meio de um link simbólico chamado sh , em relação a esses testes, ele se comporta como um traço.
por 06.11.2018 / 23:45
0

Para o bash shell, achei a resposta do @Dennis Williamson mais útil, mas não funcionou o caso de sudo . Isso faz:

if ( [[ $_ != $0 ]] && [[ $_ != $SHELL ]] ); then
    echo "I'm being sourced!"
    exit 1
fi
    
por 21.03.2013 / 21:59
0

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.

    
por 19.02.2019 / 02:30
0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (para bash obviamente)


$BASH_SOURCE casos de teste

dado arquivo /tmp/source1.sh

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

source o arquivo de diferentes maneiras

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)

em relação a $0

em todos os casos, se o script tiver o comando adicionado

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

então source o script sempre foi 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)
    
por 06.03.2019 / 07:03
-2

Na verdade, "dirname $ 0" vai te dar o caminho para o script, mas você tem que interpretar um pouco:

$ cat bash0
#!/bin/bash
echo \
$ cat bash0
#!/bin/bash
echo \%pre%=$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
=$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

Você precisa se preparar para lidar com "." como o nome do diretório sob algumas circunstâncias comuns. Eu experimentei um pouco, já que eu me lembro do dirname embutido no ksh fazendo as coisas um pouco diferente quando "." aparece no PATH.

    
por 08.12.2010 / 17:15

Tags