Como detectar se um script está sendo originado


217

Eu tenho um script no qual não quero que ele chame exitse estiver sendo originado.

Pensei em verificar se, $0 == bashmas isso tem problemas, se o script é originário de outro script ou se o usuário o origina de um shell diferente, como ksh.

Existe uma maneira confiável de detectar se um script está sendo originado?


2
Há algum tempo, tive um problema semelhante e o resolvi, evitando a 'saída' em todos os casos; "kill -INT $$" finaliza o script com segurança em ambos os casos.
JESii

1
Você percebeu esta resposta ? É dado 5 anos depois do aceito, mas tem "pilhas incluídas".
raratiru 7/01/19

Respostas:


72

Isso parece ser portátil entre Bash e Korn:

[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"

Uma linha semelhante a esta ou uma tarefa como pathname="$_"(com um teste e ação posteriores) deve estar na primeira linha do script ou na linha após o shebang (que, se usado, deve ser para ksh para que ele funcione sob mais circunstâncias).


10
Infelizmente não é garantido que funcione. Se o usuário tiver definido BASH_ENV, $_na parte superior do script será executado o último comando BASH_ENV.
11117 Mikel

30
Isso também não funcionará se você usar o bash para executar o script, por exemplo, $ bash script.sh, o $ _ seria / bin / bash em vez de ./script.sh, que é o caso que você espera, quando o script é chamado desta maneira: $ ./script.sh De qualquer forma, detectar com $_é um problema.
Wirawan Purwanto

2
Testes adicionais podem ser incluídos para verificar esses métodos de chamada.
Pausado até novo aviso.

8
Infelizmente, isso está errado! veja a minha resposta
F. Hauri

8
Para resumir: Embora essa abordagem normalmente funcione, ela não é robusta ; falha nos 2 cenários a seguir: (a) bash script(invocação via executável do shell, que esta solução relata incorretamente como originada ) e (b) (muito menos provável) echo bash; . script(se $_coincidir com a origem do shell do script, esta solução a denuncia incorretamente como um subshell ). Somente variáveis ​​especiais específicas do shell (por exemplo, $BASH_SOURCE) permitem soluções robustas (segue-se que não há uma solução robusta compatível com POSIX). Ele é possível, embora pesado, para elaborar um teste cross-shell robusta.
precisa

170

Se sua versão do Bash souber da variável de matriz BASH_SOURCE, tente algo como:

# man bash | less -p BASH_SOURCE
#[[ ${BASH_VERSINFO[0]} -le 2 ]] && echo 'No BASH_SOURCE array variable' && exit 1

[[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo "script ${BASH_SOURCE[0]} is being sourced ..."

11
Essa é talvez a maneira mais limpa, pois $ BASH_SOURCE se destina exatamente a esse propósito.
con-f-use

4
Observe que isso não funcionará no ksh, que é uma condição especificada pelo OP.
Pausado até novo aviso.

2
Existe uma razão para usar em ${BASH_SOURCE[0]}vez de apenas $BASH_SOURCE? E ${0}vs $0?
Hraban

4
BASH_SOURCEé uma variável de matriz (consulte o manual ) que contém um rastreamento de pilha de fontes, onde ${BASH_SOURCE[0]}é a mais recente. Os colchetes são usados ​​aqui para dizer ao bash o que faz parte do nome da variável. Eles não são necessários $0neste caso, mas também não machucam. ;)
Konrad

4
@ Konrad, e se você expandir $array, obtém ${array[0]}por padrão. Então, novamente, há uma razão [...]?
Charles Duffy

133

Soluções robustas para bash, ksh,zsh , incluindo uma cruz-concha uma, além de uma solução compatível com POSIX razoavelmente robustas :

  • Os números de versão fornecidos são aqueles em que a funcionalidade foi verificada - provavelmente, essas soluções também funcionam em versões muito anteriores - bem-vindo feedback .

  • Usando apenas recursos POSIX (como in dash, que atua como /bin/shUbuntu), não uma maneira robusta de determinar se um script está sendo originado - veja abaixo a melhor aproximação .

Seguidores de uma linha seguem - explicação abaixo; a versão cross-shell é complexa, mas deve funcionar robusta:

  • bash (verificado em 3.57 e 4.4.19)

    (return 0 2>/dev/null) && sourced=1 || sourced=0
  • ksh (verificado em 93u +)

    [[ $(cd "$(dirname -- "$0")" && 
       printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
         sourced=1 || sourced=0
  • zsh (verificado em 5.0.5) - certifique-se de chamar isso fora de uma função

    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
  • shell cruzado (bash, ksh, zsh)

    ([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] || 
     [[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
        printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] || 
     [[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
  • Compatível com POSIX ; não é uma linha (tubulação única) por razões técnicas e não é totalmente robusta (veja abaixo):

    sourced=0
    if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
      case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
    elif [ -n "$KSH_VERSION" ]; then
      [ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
    elif [ -n "$BASH_VERSION" ]; then
      (return 0 2>/dev/null) && sourced=1 
    else # All other shells: examine $0 for known shell binary filenames
      # Detects `sh` and `dash`; add additional shell filenames as needed.
      case ${0##*/} in sh|dash) sourced=1;; esac
    fi

Explicação:


festança

(return 0 2>/dev/null) && sourced=1 || sourced=0

Nota: A técnica foi adaptada da resposta do usuário5754163 , pois se mostrou mais robusta que a solução original, [[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0[1]

  • O Bash permite returninstruções apenas de funções e, no escopo de nível superior de um script, somente se o script for originado .

    • Se returnfor usado no escopo de nível superior de um script não originado , uma mensagem de erro será emitida e o código de saída será definido como 1.
  • (return 0 2>/dev/null)executa returnem uma subshell e suprime a mensagem de erro; posteriormente, o código de saída indica se o script foi originado ( 0) ou não ( 1), que é usado com os operadores &&e ||para definir a sourcedvariável de acordo.

    • O uso de um subshell é necessário, porque a execução return no escopo de nível superior de um script de origem sairia do script.
    • Dica simples para @Haozhun , que tornou o comando mais robusto usando explicitamente 0como o returnoperando; ele observa: por ajuda do bash de return [N]: "Se N for omitido, o status de retorno é o do último comando". Como resultado, a versão anterior [que usou apenas return, sem um operando] produz resultado incorreto se o último comando no shell do usuário tiver um valor de retorno diferente de zero.

ksh

[[ \
   $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != \
   "${.sh.file}" \
]] && 
sourced=1 || sourced=0

Variável especial ${.sh.file}é algo análogo a $BASH_SOURCE; observe que ${.sh.file}causa um erro de sintaxe no bash, zsh e dash, portanto, execute-o condicionalmente em scripts de vários shell.

Ao contrário do bash, $0e ${.sh.file}não são garantidos para ser exatamente idêntica no caso não-originária, como $0pode ser um parente caminho, enquanto ${.sh.file}é sempre um completo caminho, por isso $0deve ser resolvido para um caminho completo antes de comparar.


zsh

[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0

$ZSH_EVAL_CONTEXTcontém informações sobre o contexto da avaliação - chame isso fora de uma função. Dentro de um script de origem [escopo de nível superior], $ZSH_EVAL_CONTEXT termina com :file.

Advertência: Dentro de uma substituição de comando, o zsh acrescenta :cmdsubst, então teste $ZSH_EVAL_CONTEXTpor :file:cmdsubst$lá.


Usando apenas recursos POSIX

Se você está disposto a fazer certas suposições, você pode fazer uma razoável, mas não tolo à prova de palpite sobre se o script está sendo originada, com base em saber os nomes de arquivos binários das conchas que podem estar cumprindo seu script .
Notavelmente, isso significa que essa abordagem falhará se o seu script estiver sendo originado por outro script .

A seção "Como lidar com chamadas de origem" nesta resposta minha discute os casos extremos que não podem ser tratados com os recursos do POSIX apenas em detalhes.

Isso depende do comportamento padrão de $0, que zsh, por exemplo, não exibe.

Portanto, a abordagem mais segura é combinar os métodos robustos e específicos de shell acima com uma solução de fallback para todas as conchas restantes.

Ponta do chapéu para Stéphane Desneux e sua resposta para a inspiração (transformando minha expressão de declaração de shell cruzado em uma declaração shcompatível ife adicionando um manipulador para outras conchas).

sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
  [ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
  (return 0 2>/dev/null) && sourced=1 
else # All other shells: examine $0 for known shell binary filenames
  # Detects `sh` and `dash`; add additional shell filenames as needed.
  case ${0##*/} in sh|dash) sourced=1;; esac
fi

[1] user1902689 descobriu que [[ $0 != "$BASH_SOURCE" ]]produz um falso positivo quando você executa um script localizado no$PATH , passando seu mero nome de arquivo para o bashbinário; por exemplo, bash my-scriptporque $0é justo my-script, enquanto $BASH_SOURCEo caminho completo . Enquanto você normalmente não usaria essa técnica para invocar scripts no $PATH- você os invocaria diretamente ( my-script) - é útil quando combinado com -xa depuração .


1
parabéns por uma resposta tão abrangente.
DrUseful

75

Depois de ler a resposta de @ DennisWilliamson, há alguns problemas, veja abaixo:

Como esta pergunta representa e , há outra parte nesta resposta referente a ... ver abaixo.

Simples maneira

[ "$0" = "$BASH_SOURCE" ]

Vamos tentar (em tempo real porque essa festança poderia ;-):

source <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

bash <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 16229 is own (/dev/fd/63, /dev/fd/63)

Em sourcevez disso, uso off .para facilitar a leitura (como .é um alias para source):

. <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

Observe que o número do processo não muda enquanto o processo permanece na origem :

echo $$
29301

Por que não usar $_ == $0comparação

Para garantir muitos casos, começo a escrever um script verdadeiro :

#!/bin/bash

# As $_ could be used only once, uncomment one of two following lines

#printf '_="%s", 0="%s" and BASH_SOURCE="%s"\n' "$_" "$0" "$BASH_SOURCE"
[[ "$_" != "$0" ]] && DW_PURPOSE=sourced || DW_PURPOSE=subshell

[ "$0" = "$BASH_SOURCE" ] && BASH_KIND_ENV=own || BASH_KIND_ENV=sourced;
echo "proc: $$[ppid:$PPID] is $BASH_KIND_ENV (DW purpose: $DW_PURPOSE)"

Copie isso para um arquivo chamado testscript:

cat >testscript   
chmod +x testscript

Agora poderíamos testar:

./testscript 
proc: 25758[ppid:24890] is own (DW purpose: subshell)

Isso está ok.

. ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

source ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

Isso está ok.

Mas, para testar um script antes de adicionar -xsinalizador:

bash ./testscript 
proc: 25776[ppid:24890] is own (DW purpose: sourced)

Ou para usar variáveis ​​predefinidas:

env PATH=/tmp/bintemp:$PATH ./testscript 
proc: 25948[ppid:24890] is own (DW purpose: sourced)

env SOMETHING=PREDEFINED ./testscript 
proc: 25972[ppid:24890] is own (DW purpose: sourced)

Isso não vai mais funcionar.

Mover o comentário da quinta linha para a sexta daria uma resposta mais legível:

./testscript 
_="./testscript", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26256[ppid:24890] is own

. testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

source testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

bash testscript 
_="/bin/bash", 0="testscript" and BASH_SOURCE="testscript"
proc: 26317[ppid:24890] is own

env FILE=/dev/null ./testscript 
_="/usr/bin/env", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26336[ppid:24890] is own

Mais difíceis: agora...

Como eu não uso muito, depois de ler na página de manual, existem minhas tentativas:

#!/bin/ksh

set >/tmp/ksh-$$.log

Copie isso em um testfile.ksh:

cat >testfile.ksh
chmod +x testfile.ksh

Do que executá-lo duas vezes:

./testfile.ksh
. ./testfile.ksh

ls -l /tmp/ksh-*.log
-rw-r--r-- 1 user user   2183 avr 11 13:48 /tmp/ksh-9725.log
-rw-r--r-- 1 user user   2140 avr 11 13:48 /tmp/ksh-9781.log

echo $$
9725

e veja:

diff /tmp/ksh-{9725,9781}.log | grep ^\> # OWN SUBSHELL:
> HISTCMD=0
> PPID=9725
> RANDOM=1626
> SECONDS=0.001
>   lineno=0
> SHLVL=3

diff /tmp/ksh-{9725,9781}.log | grep ^\< # SOURCED:
< COLUMNS=152
< HISTCMD=117
< LINES=47
< PPID=9163
< PS1='$ '
< RANDOM=29667
< SECONDS=23.652
<   level=1
<   lineno=1
< SHLVL=2

Há alguma variável herdada em uma execução de origem , mas nada realmente relacionado ...

Você pode até verificar se $SECONDSestá próximo 0.000, mas isso garante apenas casos de origem manual ...

Você pode tentar verificar o que é pai ou mãe:

Coloque isso no seu testfile.ksh:

ps $PPID

Do que:

./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32320 pts/4    Ss     0:00 -ksh

. ./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32319 ?        S      0:00 sshd: user@pts/4

ou ps ho cmd $PPID, mas este trabalho apenas para um nível de subsessões ...

Desculpe, não consegui encontrar uma maneira confiável de fazer isso, sob .


[ "$0" = "$BASH_SOURCE" ] || [ -z "$BASH_SOURCE" ]para scripts lidos via pipe ( cat script | bash).
hakre

2
Observe que .não é um alias para source, na verdade, é o contrário. source somescript.shé um bash-ismo e não é portátil, . somescript.shé POSIX e IIRC portátil.
precisa saber é o seguinte

32

A BASH_SOURCE[]resposta (bash-3.0 e posterior) parece mais simples, embora nãoBASH_SOURCE[] esteja documentada para funcionar fora de um corpo de função (atualmente funciona, em desacordo com a página do manual).

A maneira mais robusta, como sugerido por Wirawan Purwanto, é verificar FUNCNAME[1] dentro de uma função :

function mycheck() { declare -p FUNCNAME; }
mycheck

Então:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

Isso equivale a verificar a saída de caller, os valores maine sourcedistinguir o contexto do chamador. Usar FUNCNAME[]salva a captura e a análise de callersaída. Você precisa conhecer ou calcular a profundidade da chamada local para estar correta. Casos como um script originário de outra função ou script farão com que a matriz (pilha) seja mais profunda. ( FUNCNAMEé uma variável de matriz bash especial, ela deve ter índices contíguos correspondentes à pilha de chamadas, desde que nunca seja unset.)

function issourced() {
    [[ ${FUNCNAME[@]: -1} == "source" ]]
}

(No bash-4.2 e posterior, você pode usar a forma mais simples ${FUNCNAME[-1]}para o último item da matriz. Melhorado e simplificado graças ao comentário de Dennis Williamson abaixo.)

No entanto, seu problema, conforme declarado, é " Eu tenho um script em que não quero que ele chame 'exit' se estiver sendo originado ". O bashidioma comum para esta situação é:

return 2>/dev/null || exit

Se o script estiver sendo originado, returnele terminará e retornará ao chamador.

Se o script estiver sendo executado, returnretornará um erro (redirecionado) e exitencerrará o script normalmente. Ambos returne exitpodem usar um código de saída, se necessário.

Infelizmente, isso não funciona ksh(pelo menos não na versão derivada da AT&T que eu tenho aqui), trata returncomo equivalente a exitse invocado fora de uma função ou script de origem de pontos.

Atualizado : o que você pode fazer nas versões contemporâneas kshé verificar a variável especial .sh.leveldefinida para a profundidade da chamada da função. Para um script invocado, isso será inicialmente desconfigurado; para um script de origem de pontos, será definido como 1.

function issourced {
    [[ ${.sh.level} -eq 2 ]]
}

issourced && echo this script is sourced

Isso não é tão robusto quanto a versão do bash, você deve chamar issourced()no arquivo do qual está testando no nível superior ou em uma profundidade de função conhecida.

(Você também pode estar interessado neste código no github, que usa uma kshfunção de disciplina e alguns truques de trap de depuração para emular a FUNCNAMEmatriz bash .)

A resposta canônica aqui: http://mywiki.wooledge.org/BashFAQ/109 também oferece $-como outro indicador (embora imperfeito) do estado do shell.


Notas:

  • é possível criar funções bash denominadas "main" e "source" ( substituindo o builtin ), esses nomes podem aparecer, FUNCNAME[]mas enquanto apenas o último item dessa matriz for testado, não haverá ambiguidade.
  • Eu não tenho uma boa resposta para pdksh. A coisa mais próxima que posso encontrar se aplica apenas a pdksh, onde cada fonte de um script abre um novo descritor de arquivo (começando com 10 para o script original). Quase certamente não é algo em que você deseja confiar ...

Que tal ${FUNCNAME[(( ${#FUNCNAME[@]} - 1 ))]}obter o último item (inferior) da pilha? Então testar contra "main" (negar para OP) foi o mais confiável para mim.
Adrian Günter

Se eu tiver um PROMPT_COMMANDconjunto, será FUNCNAMEexibido como o último índice da matriz, se eu executar source sourcetest.sh. Invertendo o cheque (à procura de maincomo o último índice) parece mais robusto: is_main() { [[ ${FUNCNAME[@]: -1} == "main" ]]; }.
Dimo414

1
A página de manual declara que FUNCNAMEestá disponível apenas em funções. De acordo com meus testes com declare -p FUNCNAME, bashcomporta-se de maneira diferente. a v4.3 apresenta um erro fora das funções, enquanto a v4.4 fornece declare -a FUNCNAME. Ambos (!) De retorno mainpara ${FUNCNAME[0]}no script principal (se for executado), enquanto $FUNCNAMEnão dá nada. E: Existem tantos scripts por aí "ab" usando $BASH_SOURCEfunções externas, que duvido que isso possa ou será alterado.
Tino

24

Nota do editor: A solução desta resposta funciona de maneira robusta, mas é bashapenas. Pode ser simplificado para
(return 2>/dev/null).

TL; DR

Tente executar uma returndeclaração. Se o script não for originado, isso gerará um erro. Você pode pegar esse erro e prosseguir conforme necessário.

Coloque isso em um arquivo e chame-o, digamos, test.sh:

#!/usr/bin/env sh

# Try to execute a `return` statement,
# but do it in a sub-shell and catch the results.
# If this script isn't sourced, that will raise an error.
$(return >/dev/null 2>&1)

# What exit code did that give?
if [ "$?" -eq "0" ]
then
    echo "This script is sourced."
else
    echo "This script is not sourced."
fi

Execute-o diretamente:

shell-prompt> sh test.sh
output: This script is not sourced.

Fonte:

shell-prompt> source test.sh
output: This script is sourced.

Para mim, isso funciona no zsh e no bash.

Explicação

A returninstrução gerará um erro se você tentar executá-lo fora de uma função ou se o script não for originado. Tente isso em um prompt de shell:

shell-prompt> return
output: ...can only `return` from a function or sourced script

Você não precisa ver essa mensagem de erro, para redirecionar a saída para dev / null:

shell-prompt> return >/dev/null 2>&1

Agora verifique o código de saída. 0 significa OK (nenhum erro ocorreu), 1 significa que ocorreu um erro:

shell-prompt> echo $?
output: 1

Você também deseja executar a returninstrução dentro de um sub-shell. Quando a returninstrução a executa. . . bem . . . retorna. Se você executá-lo em um sub-shell, ele retornará a partir desse sub-shell, em vez de retornar ao seu script. Para executar no sub-shell, envolva-o em $(...):

shell-prompt> $(return >/dev/null 2>$1)

Agora, você pode ver o código de saída do sub-shell, que deve ser 1, porque ocorreu um erro dentro do sub-shell:

shell-prompt> echo $?
output: 1

Isso falha para mim no 0.5.8-2.1ubuntu2 $ readlink $(which sh) dash $ . test.sh This script is sourced. $ ./test.sh This script is sourced.
Phil Rutschman 27/09/16

3
O POSIX não especifica o que returndeve ser feito no nível superior ( pubs.opengroup.org/onlinepubs/9699919799/utilities/… ). O dashshell trata um returnno nível superior como exit. Outras conchas gostam bashou zshnão permitem returnno nível superior, que é o recurso que uma técnica como essa explora.
user5754163

Ele funciona em sh se você remover o $antes do subshell. Ou seja, use em (return >/dev/null 2>&1)vez de $(return >/dev/null 2>&1)- mas depois ele pára de funcionar no bash.
epónimo

@ Simónimo: Como dash, onde esta solução não funciona, atua como shno Ubuntu, por exemplo, essa solução geralmente não funciona sh. A solução funciona para mim no Bash 3.2.57 e 4.4.5 - com ou sem o $antes (...)(embora nunca haja uma boa razão para isso $).
mklement0

2
returnUm valor de retorno explícito é interrompido ao executar sourceos scripts logo após um comando com saída incorreta. Propôs a edição de aprimoramento.
DimG

12

FWIW, depois de ler todas as outras respostas, criei a seguinte solução para mim:

Atualização: Na verdade, alguém viu um erro corrigido em outra resposta que também afetou a minha. Eu acho que a atualização aqui também é uma melhoria (veja as edições se você estiver curioso).

Isso funciona para todos os scripts, que começam com,#!/bin/bash mas também podem ser fornecidos por diferentes shells, para aprender algumas informações (como configurações) que são mantidas fora da mainfunção.

De acordo com os comentários abaixo, esta resposta aqui aparentemente não funciona para todas as bashvariantes. Também não é para sistemas, onde /bin/shé baseado bash. IE falha para bashv3.x no MacOS. (Atualmente eu não sei como resolver isso.)

#!/bin/bash

# Function definitions (API) and shell variables (constants) go here
# (This is what might be interesting for other shells, too.)

# this main() function is only meant to be meaningful for bash
main()
{
# The script's execution part goes here
}

BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "$@"

Em vez das duas últimas linhas, você pode usar o código a seguir (na minha opinião menos legível) para não definir BASH_SOURCEoutras conchas e permitir set -etrabalhar em main:

if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "$@"; fi

Esta receita de script possui as seguintes propriedades:

  • Se executado por bash da maneira normal, mainé chamado. Observe que isso não inclui uma chamada como bash -x script(onde scriptnão contém um caminho), veja abaixo.

  • Se proveniente de bash,main é chamado apenas, se o script de chamada tiver o mesmo nome. (Por exemplo, se ele se origina ou via bash -c 'someotherscript "$@"' main-script args..onde main-scriptdeve estar, o que testvê como $BASH_SOURCE).

  • Se originado / executado / lido / evaled por um shell que não sejabash , mainnão é chamado ( BASH_SOURCEsempre é diferente de $0).

  • mainnão é chamado se bashlê o script de stdin, a menos que você defina $0a string vazia da seguinte maneira:( exec -a '' /bin/bash ) <script

  • Se avaliado por bashcom eval ( eval "`cat script`" todas as aspas são importantes! ) De outro script, isso chama main. Se evalfor executado diretamente da linha de comando, será semelhante ao caso anterior, em que o script é lido a partir de stdin. (BASH_SOURCE fica em branco, enquanto $0normalmente /bin/bashnão é forçado a algo completamente diferente.)

  • E se main não for chamado, ele retornará true( $?=0).

  • Isso não depende de comportamento inesperado (anteriormente escrevi sem documentos, mas não encontrei nenhuma documentação que você também não possa unsetalterar BASH_SOURCE):

    • BASH_SOURCE é uma matriz reservada do bash . Mas permitir BASH_SOURCE=".$0"a mudança abriria uma lata de vermes muito perigosa, então minha expectativa é que isso não tenha efeito (exceto, talvez, algum aviso feio apareça em alguma versão futura do bash).
    • Não há documentação que BASH_SOURCEfuncione fora das funções. No entanto, o oposto (que só funciona em funções) também não está documentado. A observação é que ele funciona (testado nas bashv4.3 e v4.4, infelizmente não bashtenho mais a v3.x) e que muitos scripts quebrariam, se$BASH_SOURCE parasse de funcionar como observado. Portanto, minha expectativa é que isso BASH_SOURCEpermaneça como está para as versões futuras bashtambém.
    • Por outro lado (boa descoberta, BTW!) ( return 0 ), Considere , o que fornece 0se originado e 1se não for originado. Isso é um pouco inesperado, não apenas para mim , e (de acordo com as leituras), o POSIX diz que o returnsubshell é um comportamento indefinido (e o returnaqui é claramente de um subshell). Talvez esse recurso acabe recebendo amplo uso suficiente para que não possa mais ser alterado, mas no AFAICS há uma chance muito maior de que alguma bashversão futura acidental mude o comportamento de retorno nesse caso.
  • Infelizmente bash -x script 1 2 3não funciona main. (Compare script 1 2 3onde scriptnão há caminho). A seguir pode ser usado como solução alternativa:

    • bash -x "`which script`" 1 2 3
    • bash -xc '. script' "`which script`" 1 2 3
    • Isso bash script 1 2 3não é executado mainpode ser considerado um recurso.
  • Observe que as ( exec -a none script )chamadas main( bashnão passam$0 para o script, para isso você precisa usar -ccomo mostrado no último ponto).

Assim, exceto em alguns casos de canto, mainsó é chamado quando o script é executado da maneira usual. Normalmente é isso que você quer, especialmente porque não possui código complexo e difícil de entender.

Observe que é muito semelhante ao código Python:

if __name__ == '__main__': main()

O que também impede a chamada de main , exceto em alguns casos de canto, pois você pode importar / carregar o script e impor esse__name__='__main__'

Por que acho que essa é uma boa maneira geral de resolver o desafio

Se você tiver algo que possa ser originado por várias conchas, ele deverá ser compatível. No entanto (leia as outras respostas), pois não há uma maneira (fácil de implementar) portátil de detectar osource ing, você deve alterar as regras .

Ao impor que o script deve ser executado por /bin/bash , você faz exatamente isso.

Isso resolve todos os casos, mas , nesse caso, o script não pode ser executado diretamente:

  • /bin/bash não está instalado ou não funciona (por exemplo, em um ambiente de inicialização)
  • Se você o colocar em uma concha como em curl https://example.com/script | $SHELL
  • (Observação: isso só é verdade se você bashé recente o suficiente. É relatado que esta receita falha em determinadas variantes. Portanto, verifique se ela funciona no seu caso.)

No entanto, não consigo pensar em nenhuma razão real em que você precise disso e também na capacidade de obter o mesmo script em paralelo! Geralmente você pode envolvê-lo para executar o mainmanualmente. Curtiu isso:

  • $SHELL -c '. script && main'
  • { curl https://example.com/script && echo && echo main; } | $SHELL
  • $SHELL -c 'eval "`curl https://example.com/script`" && main'
  • echo 'eval "`curl https://example.com/script`" && main' | $SHELL

Notas

  • Esta resposta não teria sido possível sem a ajuda de todas as outras respostas! Mesmo os errados - o que inicialmente me fez postar isso.

  • Atualização: Editada devido às novas descobertas encontradas em https://stackoverflow.com/a/28776166/490291


Testado para ksh e bash-4.3. Agradável. É uma pena que sua resposta tenha uma vida difícil, já que as outras respostas já tiveram anos acumulando votos positivos.
hagello

obrigado por esta resposta. Apreciei o teste mais longo e 'menos legível' com a instrução IF, pois é bom lidar com ambas as situações para, pelo menos, causar falhas não silenciosas. No meu caso, preciso que um script seja fornecido ou informe o usuário sobre seu erro ao não usar o código-fonte.
21419 Tim

@Tino: Quanto a "também pode ser obtido por diferentes shells": no macOS, onde /bin/shestá efetivamente bashno modo POSIX, atribuindo a BASH_SOURCE quebra de seu script. Em outras conchas ( dash, ksh, zsh), invocando o seu script passando-a como um argumento de arquivo diretamente para o executável shell avarias (por exemplo, zsh <your-script>vai fazer o seu roteiro engano pensar que é originária ). (Você já mencionar que canalizando o mau funcionamento do código, em todas as conchas.)
mklement0

@Tino: Como um aparte: Embora . <your-script>(sourcing) funcione com todos os shells do tipo POSIX, em princípio, só faz sentido se o script foi escrito explicitamente para usar somente os recursos do POSIX, de modo a impedir que recursos específicos de um shell interrompam a execução em outras conchas; usar uma linha shebang do Bash (em vez de #!/bin/sh) é, portanto, confuso - pelo menos sem um comentário conspícuo. Por outro lado, se seu script for executado apenas a partir do Bash (mesmo que não considerando quais recursos talvez não sejam portáteis), é melhor recusar a execução em shells que não sejam do Bash.
precisa saber é o seguinte

1
@ mklement0 Mais uma vez obrigado, acrescentou que há um problema. Para outros leitores: Quando adquirido com o bash v3.x, ele não deve ser executado main, mas faz isso neste caso! E quando originados por /bin/sh, ou seja bash --posix, o mesmo acontece neste caso, e isso também está errado.
Tino

6

Isso funciona mais tarde no script e não depende da variável _:

## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename $0) = $Prog ]; then
   exit 1  # not sourced
fi

ou

[ $(basename $0) = $Prog ] && exit

1
Penso que esta resposta é uma das poucas compatíveis com POSIX aqui. Com as desvantagens óbvias, é que você precisa saber o nome do arquivo e ele não funciona se os dois scripts tiverem o mesmo nome.
JepZ

5

Vou dar uma resposta específica ao BASH. Korn shell, desculpe. Suponha que seu nome de script seja include2.sh; então faça uma função dentro do include2.shchamado am_I_sourced. Aqui está minha versão demo de include2.sh:

am_I_sourced()
{
  if [ "${FUNCNAME[1]}" = source ]; then
    if [ "$1" = -v ]; then
      echo "I am being sourced, this filename is ${BASH_SOURCE[0]} and my caller script/shell name was $0"
    fi
    return 0
  else
    if [ "$1" = -v ]; then
      echo "I am not being sourced, my script/shell name was $0"
    fi
    return 1
  fi
}

if am_I_sourced -v; then
  echo "Do something with sourced script"
else
  echo "Do something with executed script"
fi

Agora tente executá-lo de várias maneiras:

~/toys/bash $ chmod a+x include2.sh

~/toys/bash $ ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ bash ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ . include2.sh
I am being sourced, this filename is include2.sh and my caller script/shell name was bash
Do something with sourced script

Portanto, isso funciona sem exceção, e não está usando o $_material quebradiço . Este truque usa o recurso de introspecção do BASH, ou seja, variáveis ​​embutidas FUNCNAMEeBASH_SOURCE ; consulte a documentação deles na página de manual do bash.

Apenas duas ressalvas:

1) a chamada para am_I_called deve ocorrer no script de origem, mas não dentro de nenhuma função, para que não ${FUNCNAME[1]}retorne outra coisa. Sim ... você poderia ter verificado${FUNCNAME[2]} - mas você apenas torna sua vida mais difícil.

2) A função am_I_called deve residir no script de origem se você quiser descobrir qual o nome do arquivo que está sendo incluído.


1
Esclarecimento: Este recurso requer o BASH versão 3+ para funcionar. No BASH 2, FUNCNAME é uma variável escalar em vez de uma matriz. Além disso, o BASH 2 não possui a variável de matriz BASH_SOURCE.
Wirawan Purwanto

4

Gostaria de sugerir uma pequena correção à resposta muito útil de Dennis , para torná-la um pouco mais portátil, espero:

[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"

porque [[não é reconhecido pelo shell compatível Debian POSIX (um pouco retentivo anal) dash. Além disso, pode-se precisar das aspas para proteger contra nomes de arquivos contendo espaços, novamente no referido shell.


2

$_é bastante quebradiço. Você deve verificá-lo como a primeira coisa que faz no script. E mesmo assim, não é garantido que contenha o nome do seu shell (se originado) ou o nome do script (se executado).

Por exemplo, se o usuário definiu BASH_ENV, na parte superior de um script, $_contém o nome do último comando executado no diretórioBASH_ENV script.

A melhor maneira que eu encontrei é usar $0assim:

name="myscript.sh"

main()
{
    echo "Script was executed, running main..."
}

case "$0" in *$name)
    main "$@"
    ;;
esac

Infelizmente, esse caminho não funciona imediatamente no zsh devido à functionargzeroopção de fazer mais do que o nome sugere e estar ativado por padrão.

Para contornar isso, eu coloquei unsetopt functionargzerono meu .zshenv.


1

Eu segui mklement0 expressão compacta .

Isso é legal, mas notei que ele pode falhar no caso do ksh quando invocado da seguinte maneira:

/bin/ksh -c ./myscript.sh

(ele acha que é originário e não porque executa um subshell). Mas a expressão funcionará para detectar isso:

/bin/ksh ./myscript.sh

Além disso, mesmo que a expressão seja compacta, a sintaxe não é compatível com todos os shells.

Então terminei com o seguinte código, que funciona para bash, zsh, dash e ksh

SOURCED=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && SOURCED=1
elif [ -n "$KSH_VERSION" ]; then
    [[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ]] && SOURCED=1
elif [ -n "$BASH_VERSION" ]; then
    [[ $0 != "$BASH_SOURCE" ]] && SOURCED=1
elif grep -q dash /proc/$$/cmdline; then
    case $0 in *dash*) SOURCED=1 ;; esac
fi

Sinta-se livre para adicionar conchas exóticas suporte :)


Em ksh 93+u, ksh ./myscript.shfunciona bem para mim (com a minha declaração) - qual versão você está usando?
mklement0

Receio que não haja maneira de determinar com segurança se um script está sendo originado usando apenas recursos POSIX: sua tentativa assume o Linux ( /proc/$$/cmdline) e se concentra dashapenas (que também atua como o shUbuntu, por exemplo). Se você estiver disposto a fazer certas suposições, poderá examinar $0um teste razoável - mas incompleto - que seja portátil.
mklement0

++ para a abordagem básica - tomei a liberdade de adaptá-la para o que eu acho que é a melhor aproximação portátil de suporte sh/ dashtambém, em um adendo à minha resposta.
mklement0

0

Eu não acho que exista uma maneira portátil de fazer isso no ksh e no bash. No bash, você pode detectá-lo usando a callersaída, mas não acho que exista equivalente no ksh.


$0trabalha em bash, ksh93, e pdksh. Eu não tenho ksh88que testar.
11117 Mikel

0

Eu precisava de um one-liner que funcione em [mac, linux] com bash.version> = 3 e nenhuma dessas respostas se encaixa na conta.

[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"

1
A bashsolução funciona bem (você pode simplificar $BASH_SOURCE), mas a kshsolução não é robusta: se o seu script estiver sendo originado por outro script , você receberá um falso positivo.
mklement0

0

Direto ao ponto: você deve avaliar se a variável "$ 0" é igual ao nome do seu Shell.


Como isso:

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi


Via SHELL :

$ bash check_source.sh 
First Parameter: check_source.sh

The script WAS NOT sourced.

Via SOURCE :

$ source check_source.sh
First Parameter: bash

The script was sourced.



É muito difícil ter uma maneira 100% portátil de detectar se um script foi originado ou não.

Em relação à minha experiência (7 anos com Shellscripting) , a única maneira segura (sem depender de variáveis ​​de ambiente com PIDs e assim por diante), que não é segura devido ao fato de ser algo VARIÁVEL ), você deve:

  • estender as possibilidades do seu se
  • usando switch / case, se você quiser.

Ambas as opções não podem ser dimensionadas automaticamente, mas é a maneira mais segura.



Por exemplo:

quando você origina um script por meio de uma sessão SSH, o valor retornado pela variável "$ 0" (ao usar origem ) é -bash .

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" || "$0" == "-bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi

OU

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
elif [[ "$0" == "-bash" ]] ; then
    echo "The script was sourced via SSH session."
else
    echo "The script WAS NOT sourced."
fi

2
Voto negativo, pois isso está errado: /bin/bash -c '. ./check_source.sh'The script WAS NOT sourced.. Mesmo bug: ln -s /bin/bash pumuckl; ./pumuckl -c '. ./check_source.sh'->The script WAS NOT sourced.
Tino

2
Seu voto negativo mudou todo o cenário e deu uma grande contribuição, Tino. Obrigado!
Ivanleoncz 4/12

0

Acabei checando [[ $_ == "$(type -p "$0")" ]]

if [[ $_ == "$(type -p "$0")" ]]; then
    echo I am invoked from a sub shell
else
    echo I am invoked from a source command
fi

Quando usado curl ... | bash -s -- ARGSpara executar scripts remotos on-the-fly, o $ 0 será apenas em bashvez do normal /bin/bashquando executar o arquivo de script real, então eu usotype -p "$0" para mostrar o caminho completo do bash.

teste:

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)
relpath /a/b/c/d/e /a/b/CC/DD/EE

wget https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath
chmod +x relpath
./relpath /a/b/c/d/e /a/b/CC/DD/EE

0

Esta é uma derivação de algumas outras respostas, em relação ao suporte de shell cruzado "universal". É reconhecidamente muito semelhante a https://stackoverflow.com/a/2942183/3220983 em particular, embora um pouco diferente. O ponto fraco disso é que um script de cliente deve respeitar como usá-lo (ou seja, exportando uma variável primeiro). A força é que isso é simples e deve funcionar "em qualquer lugar". Aqui está um modelo para o seu prazer de cortar e colar:

# NOTE: This script may be used as a standalone executable, or callable library.
# To source this script, add the following *prior* to including it:
# export ENTRY_POINT="$0"

main()
{
    echo "Running in direct executable context!"
}

if [ -z "${ENTRY_POINT}" ]; then main "$@"; fi

Nota: exportApenas certifique-se de que esse mecanismo possa ser estendido em subprocessos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.