Diferenciando entre executar e ser originado em um script de shell bash?

18

Ou o que eu estou perguntando aqui é extremamente não ortodoxo / não convencional / arriscado, ou minhas habilidades com o Google-fu simplesmente não são fáceis de entender ...

Em um script de shell bash , há alguma maneira fácil de dizer se ele está sendo originado por outro script de shell ou está sendo executado sozinho? 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 script de shell semelhante a utilitários contendo bash funções que podem ser disponibilizadas quando originadas. Quando este script está sendo executado por si só, eu vou gostar de executar certas operações, com base nas funções definidas também. Existe algum tipo de variável de ambiente que este script de shell pode pegar, 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 as variáveis de sinalização.

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

    
por h.j.k. 06.07.2015 / 04:23

3 respostas

17

Sim - a variável $ 0 fornece o nome do script durante a execução:

$ 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 é executado 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ê tem essa idéia (espero).

Atualizado para incluir BASH_SOURCE - obrigado h.j.k

    
por 06.07.2015 / 04:33
6

Combinar a resposta @ DarkHeart com a variável de ambiente BASH_SOURCE parece 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 ser uma solução mais simples ainda se eu fosse apenas contar o número de elementos na matriz BASH_SOURCE :

if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "running directly"; else echo "sourced from $0"; fi
    
por 06.07.2015 / 04:55
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
}

O array FUNCNAME mantido 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 o chamou e assim por diante.

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

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

A função acima só funciona quando chamada no nível do script (que é tudo 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, é necessário encontrar o topo da pilha e testar esse valor, o que é mais complicado.

Se você precisar, você 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 #.

Estas três funções são usadas juntas para produzir um rastreamento de pilha de função similar ao do Python e podem lhe dar 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
}

Note que FUNCNAME, BASH_SOURCE e BASH_LINENO são 3 arrays mantidos pelo bash como se fossem um array tridimensional.

    
por 11.07.2015 / 15:05