determinando o caminho para o script de shell de origem

80

Existe uma maneira de um script de 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 shell atual, portanto $0ainda é 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 uma maneira melhor?

Para ser claro, estou adquirindo o script, não o executando:

source foo.bash
Cascabel
fonte
questão relacionada que tem mais de 4200 upvotes: stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Respostas:

65

Em tcsh, $_no início do script conterá o local se o arquivo foi originado e $0contém-lo se ele 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"
Dennis Williamson
fonte
Eu apenas tive ocasião de usar isso no tcsh e notei que ele não funciona sem o shebang. Parece um pouco estranho para o comportamento de mudar se você está apenas terceirização, não executá-lo ...
Cascabel
A versão do tcsh também parece não funcionar se o script for originado de maneira não interativa (por exemplo, de um cshrc). Não consigo encontrar uma maneira de obter as informações nesse caso. Alguma ideia?
Cascabel
Sourcing funciona para mim sem o 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. Na medida em que o fornece de maneira não interativa, o arquivo de origem é incluído no arquivo pai como se realmente fosse parte dele (indistinguivelmente) como você mencionou na sua pergunta original. Eu acho que sua solução alternativa para os parâmetros posicionais é provavelmente a melhor abordagem. No entanto, a pergunta usual é "por que você quer fazer isso" e a resposta usual para a resposta é "não faça isso - faça isso em vez disso" onde "isso" costuma ser armazenado ...
Dennis Williamson
2
@clacke: Acho que em todas as versões do Bash que eu testados a partir 2.05b para 4.2.37, incluindo 4.1.9, que .e sourcetrabalharam de forma idêntica a este respeito. Observe que $_deve ser acessado na primeira instrução do arquivo, caso contrário, ele conterá o último argumento do comando anterior. Eu gosto de incluir o shebang para minha própria referência, para que eu saiba qual shell deve ser e para o editor, para que ele use o destaque de sintaxe.
Dennis Williamson
1
Haha Obviamente, eu estava testando primeiro fazendo source, depois fazendo .. Peço desculpas por ser incompetente. Eles são realmente idênticos. Enfim, $BASH_SOURCEfunciona.
clacke
30

Eu acho que você poderia usar $BASH_SOURCEvariável. 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

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.

pbm
fonte
2
E observe que as sourcepesquisas $PATHse o nome fornecido não contiver a /. A ordem de pesquisa depende das opções do shell, consulte o manual para obter detalhes.
Gilles
1
Então, algo como mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"iria funcionar?
quer
Obrigado, uma resposta rápida e útil. Dennis ganha a marca de verificação verde por dar uma resposta tcsh também. @ Gilles: Certo, achei isso na documentação. Felizmente para o meu caso de uso, quase certamente não preciso me preocupar com isso.
Cascabel
18

Para detalhes e interesse dos pesquisadores, aqui está o que eles fazem ... É um wiki da comunidade, portanto, fique à vontade para adicionar os equivalentes de outros 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

Bater:

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

$
Shawn J. Goff
fonte
1
Eu não entendo: por quê called=$_; echo $called; echo $_? Isso não será impresso $_duas vezes?
Ciro Santilli escreveu
5
@CiroSantilli: nem sempre, leia o manual do Bash sobre o $_parâmetro especial: "Na inicialização do shell, defina o nome do caminho absoluto usado para chamar o shell ou script de shell que está sendo executado conforme transmitido no ambiente ou na lista de argumentos. Posteriormente, expande-se para o último argumento para o comando anterior, após a expansão. Também definido como o nome do caminho completo usado para chamar cada comando executado e colocado no ambiente exportado para esse comando. Ao verificar o correio, este parâmetro mantém o nome do arquivo de correio. "
Adam Rosenfield
O problema é que o arquivo de origem tem um cabeçalho, o #! /bin/shque a torna inútil na origem. Isso iniciaria uma nova instância de /bin/sh, definir variáveis ​​e sair dessa instância, mantendo a instância de chamada inalterada.
JamesThomasMoon1979 6/03
2
@ JamesThomasMoon1979: Do que você está falando? Tudo o que começa com #um script de shell é um comentário.  #!(shebang) tem seu significado especial apenas como a primeira linha de um script que é executado.   Como a primeira linha de um arquivo originário, é apenas um comentário.
Scott
17

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.

Eu achei essa linha sempre funcionando, 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 readlinko 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.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]}
}

A razão pela qual o one-liner funciona é explicada pelo uso da BASH_SOURCEvariável de ambiente e de 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 de 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 de 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á em execução. 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.

Esta 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 a partir 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]

gkb0986
fonte
Essa solução funcionou para mim no bash, enquanto a resposta selecionada funcionou apenas de forma intermitente. Eu nunca entendi por que funcionava algumas vezes e não outras (talvez eu não estivesse prestando muita atenção ao shell de fornecimento).
Jim2B
13

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.

Paul Brannan
fonte
1

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 $_, eu encontrei o seguinte: Uso 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 nessa 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 somente no bash.
  • A única diferença $0é quando o arquivo atual foi originado por outro arquivo. Nesse caso, $BASH_PROFILEcontém o nome do arquivo de origem, em vez do nome do arquivo de origem.

$0

  • No zsh, $0tem o mesmo valor que $BASH_SOURCEno bash.

$_

  • $_ é deixado intocado pelo traço e pelo ksh.
  • No bash e zsh, $_decai para o último argumento da última chamada.
  • bash inicializa $_para "bash".
  • zsh deixa $_intocado. (ao procurar, é apenas o resultado da regra do "último argumento").

Symlinks

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

ksh

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

sh

  • Quando o bash ou o zsh é chamado através de um link simbólico chamado sh, em relação a esses testes, ele se comporta como traço.
Mathieu CAROFF
fonte
0

Para o bash shell, achei a 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
Matt
fonte
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 BASH_SOURCE[0]quando definido e ${(%):-%x}}quando BASH_SOURCE [0] não estiver definido.

dols3m
fonte
0

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


$BASH_SOURCE casos de teste

arquivo 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)

sourcede diferentes caminhos relativos /tmp/ae/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)

a respeito de $0

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

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

então sourceo script sempre imprime

$0 (bash)

no entanto , se o script foi executado , por exemplo,

$> bash /tmp/source1.sh

então $0seria o valor da string /tmp/source1.sh.

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

esta resposta descreve como lsofe um pouco de magia grep é a única coisa que parece ter uma chance de trabalhar para arquivos de origem aninhados no tcsh:

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

Talvez isso não funcione com links simbólicos ou arquivos de origem, mas funcione para arquivos normais. Tomado como referência para. @kenorb Sem nome do diretório, link de leitura, BASH_SOURCE.

HemanthJabalpuri
fonte
1
Foi explicado na pergunta que $0fornece informações sobre o script em execução no momento , não sobre o código fonte.
Scott
-3

Na verdade, "dirname $ 0" fornecerá o caminho para o script, mas você deve interpretá-lo um pouco:

$ 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

Você precisa se preparar para lidar com "." como o nome do diretório em algumas circunstâncias comuns. Eu experimentaria um pouco, pois lembro do dirname embutido no ksh fazendo as coisas de maneira um pouco diferente quando "." aparece em PATH.

Bruce Ediger
fonte
4
Este é um script de origem, não um script executado. $0simplesmente contém "bash" para um shell interativo, e é tudo o que o script de origem vê.
Cascabel