Diferenciando entre execução e origem em um script de shell bash?

22

Ou o que estou perguntando aqui é extremamente pouco ortodoxo / não convencional / arriscado, ou minhas habilidades no Google-fu simplesmente não estão à altura do problema ...

Em um bashscript de shell, há alguma maneira fácil de saber se ele está sendo originado por outro script de shell ou está sendo executado por si só? Em outras palavras, é possível diferenciar entre os dois comportamentos a seguir?

# from another shell script
source myScript.sh

# from command prompt, or another shell script
./myScript.sh

O que estou pensando em fazer é criar um shell script semelhante a utilitários, contendo bashfunções que podem ser disponibilizadas quando originadas. Quando este script estiver sendo executado por si só, eu gostaria que ele executasse certas operações, com base nas funções definidas também. Existe algum tipo de variável de ambiente que este script shell pode captar, por exemplo

some_function() {
    # ...
}
if [ -z "$IS_SOURCED" ]; then
    some_function;
fi

De preferência, estou procurando uma solução que não exija que o script do chamador defina quaisquer variáveis ​​de sinalizador.

edit : Eu sei a diferença entre procurar e executar o script, o que estou tentando descobrir aqui se é possível dizer a diferença no script que está sendo usado (nos dois sentidos).

hjk
fonte
1
possível duplicata de executar script com e com 'fonte' ""
cuonglm
O @cuonglm editou minha pergunta, eu sei as diferenças entre os dois, mas estou me perguntando se posso programaticamente fazer o script de shell dizer a diferença também.
Hjk
4
@cuonglm: Não é uma duplicata. Ele não está perguntando sobre o .comando, mas sobre como detectar se um script foi originado ou executado normalmente (ou seja, em uma sub-camada).
Jander
Muito boas respostas sobre mesma pergunta no estouro de pilha: stackoverflow.com/a/28776166/96944
Jannie Theunissen

Respostas:

19

Sim - a variável $ 0 fornece o nome do script conforme foi executado:

$ cat example.sh
#!/bin/bash
script_name=$( basename ${0#-} ) #- needed if sourced no path
this_script=$( basename ${BASH_SOURCE} )
if [[ ${script_name} = ${this_script} ]] ; then
    echo "running me directly"
else
    echo "sourced from ${script_name}"
fi 

$ cat example2.sh
#!/bin/bash
. ./example.sh

Que funciona como:

$ ./example.sh
running me directly
$ ./example2.sh
example.sh sourced from example2.sh

Isso não serve para ser fonte de um shell interativo, mas você entendeu (espero).

Atualizado para incluir BASH_SOURCE - obrigado hjk

DarkHeart
fonte
Perto o suficiente. :) obstáculo pequeno que eu vou ter de especificar o nome do script, pelo menos, mas eu vou tomar esta resposta se não há outras soluções viáveis ...
HJK
7

Combinar a resposta do @ DarkHeart com a variável de ambiente BASH_SOURCEparece fazer o truque:

$ head example*.sh
==> example2.sh <==
#!/bin/bash
. ./example.sh

==> example.sh <==
#!/bin/bash
if [ "$(basename $0)" = "$(basename $BASH_SOURCE)" ]; then
    echo "running directly"
else
    echo "sourced from $0"
fi
$ ./example2.sh
sourced from ./example2.sh
$ ./example.sh
running directly

edit Parece ainda ser uma solução mais simples se eu apenas contar o número de elementos no BASH_SOURCEarray:

if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "running directly"; else echo "sourced from $0"; fi
hjk
fonte
1
Parece que encontramos a variável 'bash_source' ao mesmo tempo. :)
DarkHeart
@DarkHeart Adicionei à minha resposta o uso de contar o tamanho da matriz também ... obrigado por me indicar esse caminho! : D
hjk
1

Acabei de criar o mesmo tipo de script de biblioteca que funciona muito como o BusyBox. Nele, uso a seguinte função para testar se está sendo originada ...

function isSourced () {
  [[ "${FUNCNAME[1]}" == "source" ]]  && return 0
  return 1
}

A matriz FUNCNAME mantida pelo Bash é essencialmente uma pilha de chamadas de função. $FUNCNAME(ou ${FUNCNAME[0]}) é o nome da função atualmente em execução. ${FUNCNAME[1]}é o nome da função que a chamou e assim por diante.

O item superior é um valor especial para o próprio script. Ele conterá ...

  • a palavra "fonte" se o script estiver sendo originado
  • a palavra "main" se o script estiver sendo executado E o teste estiver sendo realizado de dentro de uma função
  • "" (nulo) se o script está sendo executado E o teste está sendo executado fora de qualquer função, isto é ... no nível do próprio script.

A função acima, na verdade, só funciona quando chamada no nível do script (que é tudo o que eu precisava). Ele falharia se chamado de dentro de outra função porque o número do item da matriz estaria errado. Para fazê-lo funcionar em qualquer lugar, é preciso encontrar o topo da pilha e testar esse valor, o que é mais complicado.

Se você precisar, pode obter o número do item da matriz do "topo da pilha" com ...

  local _top_of_stack=$(( ${#FUNCNAME[@]} - 1 ))

${#FUNCNAME[@]}é o número de itens na matriz. Como uma matriz baseada em zero, subtraímos 1 para obter o último item #.

Essas três funções são usadas juntas para produzir um rastreamento de pilha de funções semelhante ao Python e podem fornecer uma idéia melhor de como tudo isso funciona ...

function inspFnStack () {
  local T+="  "
  local _at=
  local _text="\n"
  local _top=$(inspFnStackTop)
  local _fn=${FUNCNAME[1]}; [[ $_fn =~ source|main ]]  || _fn+="()"
  local i=_top; ((--i))
  #
  _text+="$i item function call stack for $_fn ...\n"
  _text+="| L   BASH_SOURCE{BASH_LINENO called from}.FUNCNAME  \n"
  _text+="| ---------------------------------------------------\n"
  while (( $i > 0 ))
  do
    _text+="| $i ${T}$(inspFnStackItem $i)\n"
    T+="  "
    ((--i))
  done
  #
  printf "$_text\n"
  #
  return 0
}

function inspFnStackItem ()  {
  local _i=$1
  local _fn=${FUNCNAME[$_i]}; [[ $_fn =~ source|main ]]  || _fn+="()"
  local _at="${BASH_LINENO[$_i-1]}"; [[ $_at == 1 ]]  && _at="trap"
  local _item="${BASH_SOURCE[$_i]}{${_at}}.$_fn"
  #
  printf "%s" "$_item"
  return 0
}

function inspFnStackTop () {
  # top stack item is 1 less than length of FUNCNAME array stack
  printf "%d\n" $(( ${#FUNCNAME[@]} - 1 ))
  #
  return 0
}

Observe que FUNCNAME, BASH_SOURCE e BASH_LINENO são 3 matrizes mantidas pelo bash como se fossem uma matriz tridimensional.

DocSalvager
fonte
0

Só quero acrescentar que contar a matriz parece não confiável e provavelmente não se deve assumir que sourcefoi usado, pois o uso de um ponto ( .) também é muito comum (e antecede a sourcepalavra - chave).

Por exemplo, para um sourced.shscript que contém apenas echo $0:


$ . sourced.sh 
bash
$ source sourced.sh 
bash
$ chmod +x sourced.sh 
$ ./sourced.sh 
./sourced.sh
$ cat ./sourced.sh 
echo $0

As soluções de comparação sugeridas funcionam melhor.

ka1l
fonte
0

Uma maneira que também funciona ao obter a partir de um shell interativo :

if [ $BASH_LINENO -ne 0 ]; then
    some_function;
fi

A BASH_LINENOvariável também é uma matriz com todas as linhas em que a função de chamada foi executada. Será zero se você chamar o script diretamente ou um número inteiro correspondente a um número de linha.

A variável BASH_ * docs

Borisu
fonte