it-swarm-pt.com

determinando o caminho para o script Shell de origem

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
86
Cascabel

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"
68

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.

32
pbm

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]

21
gkb0986

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

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

$
18
Shawn J. Goff

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.

16
Paul Brannan

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 .

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;[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

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 com $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

  • No zsh, $0 tem o mesmo valor que $BASH_SOURCE na festança.

$_

  • $_ permanece intocado pelo traço e pelo ksh.
  • No bash e zsh, $_ decai até o último argumento da última chamada.
  • o bash inicializa $_ agredir".
  • folhas zsh $_ 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.
2
Mathieu CAROFF

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
0
Patrick Maupin

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (para 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)

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)

a respeito de $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)
0
JamesThomasMoon1979

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
0
Matt

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.

0
dols3m

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)
}
0
maoizm