Rozróżniasz uruchamianie i pozyskiwanie w skrypcie powłoki bash?

22

Albo to, o co tutaj pytam, jest wyjątkowo niekonwencjonalne / niekonwencjonalne / ryzykowne, albo moje umiejętności posługiwania się Google-fu po prostu nie wystarczą ...

Czy w bashskrypcie powłoki można w łatwy sposób stwierdzić, czy jest pozyskiwany przez inny skrypt powłoki, czy jest uruchamiany sam? Innymi słowy, czy można rozróżnić następujące dwa zachowania?

# from another shell script
source myScript.sh

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

Mam na myśli stworzenie skryptu powłoki podobnego do narzędzi, zawierającego bashfunkcje, które można udostępnić podczas pozyskiwania. Kiedy ten skrypt jest uruchamiany sam, chciałbym, aby wykonywał pewne operacje, w oparciu również o zdefiniowane funkcje. Czy istnieje jakaś zmienna środowiskowa, którą ten skrypt powłoki może pobrać, np

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

Najlepiej szukam rozwiązania, które nie wymaga skryptu wywołującego do ustawiania żadnych zmiennych flag.

edycja : Znam różnicę między pozyskiwaniem a uruchamianiem skryptu, co staram się tutaj sprawdzić, czy można odróżnić używany skrypt (na dwa sposoby).

hjk
źródło
1
możliwy duplikat uruchamiania skryptu z „.” i „source”
cuonglm
@cuonglm zredagował moje pytanie, znam różnice między nimi, ale zastanawiam się, czy mogę programowo sprawić, aby skrypt powłoki również dostrzegł różnicę.
hjk
4
@cuonglm: Nie duplikat. W ogóle nie pyta o .polecenie, ale o wykrycie, czy skrypt został pobrany lub działa normalnie (tj. W podpowłoce).
Jander
Bardzo dobre odpowiedzi na to samo pytanie przy przepełnieniu stosu: stackoverflow.com/a/28776166/96944
Jannie Theunissen

Odpowiedzi:

19

Tak - zmienna $ 0 podaje nazwę skryptu podczas jego uruchamiania:

$ 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

Który działa jak:

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

To nie zaspokaja bycia źródłem z interaktywnej powłoki, ale masz ten pomysł (mam nadzieję).

Zaktualizowano, aby zawierał BASH_SOURCE - dzięki hjk

Ciemne serce
źródło
Wystarczająco blisko. :) Mała przeszkoda, że ​​będę musiał podać przynajmniej nazwę skryptu, ale skorzystam z tej odpowiedzi, jeśli nie będzie innych realnych rozwiązań ...
hjk
7

Wydaje się, że połączenie odpowiedzi @ DarkHeart ze zmienną środowiskową BASH_SOURCE:

$ 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 Wydaje się być jeszcze prostszym rozwiązaniem, gdybym po prostu policzył liczbę elementów w BASH_SOURCEtablicy:

if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "running directly"; else echo "sourced from $0"; fi
hjk
źródło
1
Wygląda na to, że znaleźliśmy zmienną „bash_source” w tym samym czasie. :)
DarkHeart,
@DarkHeart Do mojej odpowiedzi dodałem także użycie liczenia rozmiaru tablicy ... dzięki za podpowiedź do mnie na tej ścieżce! : D
hjk
1

Właśnie stworzyłem ten sam skrypt biblioteki, który działa podobnie jak BusyBox. W nim używam następującej funkcji, aby sprawdzić, czy jest pozyskiwana ...

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

Obsługiwana przez Bash tablica FUNCNAME jest zasadniczo stosem wywołań funkcji. $FUNCNAME(lub ${FUNCNAME[0]}) to nazwa aktualnie wykonywanej funkcji. ${FUNCNAME[1]}to nazwa funkcji, która ją wywołała i tak dalej.

Najwyższy element jest specjalną wartością dla samego skryptu. Będzie zawierać ...

  • słowo „źródło”, jeśli skrypt jest pozyskiwany
  • słowo „main”, jeśli skrypt jest wykonywany ORAZ test jest wykonywany z funkcji
  • „” (null), jeśli skrypt jest wykonywany ORAZ test jest wykonywany poza jakąkolwiek funkcją, to znaczy ... na poziomie samego skryptu.

Powyższa funkcja faktycznie działa tylko wtedy, gdy jest wywoływana na poziomie skryptu (co jest wszystkim, czego potrzebuję). Nie powiedzie się, jeśli zostanie wywołany z innej funkcji, ponieważ numer elementu tablicy będzie nieprawidłowy. Aby działało w dowolnym miejscu, trzeba znaleźć górę stosu i przetestować tę wartość, co jest bardziej skomplikowane.

Jeśli potrzebujesz, możesz uzyskać numer pozycji tablicy „góra stosu” za pomocą ...

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

${#FUNCNAME[@]}to liczba elementów w tablicy. Jako tablicę zerową odejmujemy 1, aby uzyskać ostatni element #.

Te trzy funkcje są używane razem, aby utworzyć ślad stosu funkcji podobny do Pythona i mogą dać ci lepszy pomysł, jak to wszystko działa ...

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
}

Zauważ, że FUNCNAME, BASH_SOURCE i BASH_LINENO to 3 tablice obsługiwane przez bash, tak jakby były jedną trójwymiarową tablicą.

DocSalvager
źródło
0

Wystarczy dodać, że zliczanie tablicy wydaje się niewiarygodne i prawdopodobnie nie należy zakładać, że sourcezostała użyta, ponieważ użycie kropki ( .) jest również bardzo powszechne (i poprzedza sourcesłowo kluczowe).

Na przykład dla sourced.shskryptu zawierającego tylko echo $0:


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

Sugerowane rozwiązania porównawcze działają lepiej.

ka1l
źródło
0

Jednym ze sposobów, który działa również podczas pozyskiwania z interaktywnej powłoki :

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

BASH_LINENOZmienna jest tablicą ze wszystkimi liniami funkcja wywoływania został zrealizowany na. Wyzeruje się, jeśli wywołasz skrypt bezpośrednio lub liczbę całkowitą odpowiadającą numerowi linii.

Dokumenty zmiennej BASH_ *

Borisu
źródło