In einem Bash-Shell-Skript zwischen Laufen und Beschaffen unterscheiden?

21

Entweder ist das, was ich hier frage, extrem unorthodox / unkonventionell / riskant, oder meine Google-Fu-Kenntnisse sind einfach nicht ausreichend für Schnupftabak ...

Gibt es in einem bashShell-Skript eine einfache Möglichkeit, festzustellen, ob es von einem anderen Shell-Skript stammt oder von selbst ausgeführt wird? Mit anderen Worten, ist es möglich, zwischen den folgenden beiden Verhaltensweisen zu unterscheiden?

# from another shell script
source myScript.sh

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

Ich überlege, ein Shell-Skript wie ein Dienstprogramm zu erstellen, das bashFunktionen enthält, die bei der Beschaffung verfügbar gemacht werden können. Wenn dieses Skript von selbst ausgeführt wird, möchte ich, dass es bestimmte Vorgänge ausführt, die auch auf den definierten Funktionen basieren. Gibt es eine Art Umgebungsvariable, auf die dieses Shell-Skript zugreifen kann, z

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

Am liebsten suche ich eine Lösung, bei der das Aufruferskript keine Flag-Variablen setzen muss.

edit : Ich kenne den Unterschied zwischen dem Sourcing und dem Ausführen des Skripts. Ich versuche hier herauszufinden, ob es möglich ist , den Unterschied im verwendeten Skript zu erkennen (auf beide Arten).

hjk
quelle
1
Mögliches Duplikat des laufenden Skripts mit "." und mit "source"
cuonglm
@cuonglm hat meine Frage bearbeitet, ich kenne die Unterschiede zwischen beiden, aber ich frage mich, ob ich das Shell-Skript programmgesteuert dazu bringen kann, auch den Unterschied zu erkennen.
hjk
4
@ Cuonglm: Kein Duplikat. Er fragt überhaupt nicht nach dem .Befehl, sondern nach der Feststellung, ob ein Skript stammt oder normal ausgeführt wird (dh in einer Subshell).
Jander
Sehr gute Antworten auf dieselbe Frage bei Stapelüberlauf: stackoverflow.com/a/28776166/96944
Jannie Theunissen

Antworten:

19

Ja - die Variable $ 0 gibt den Namen des Skripts an, wie es ausgeführt wurde:

$ 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

Welches läuft wie:

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

Das bedeutet nicht, dass Sie eine Quelle einer interaktiven Shell sind, aber Sie bekommen diese Idee (hoffe ich).

Aktualisiert, um BASH_SOURCE einzuschließen - danke hjk

Dunkles Herz
quelle
Nahe genug. :) Kleine Hürde, dass ich mindestens den Namen des Skripts angeben muss, aber ich werde diese Antwort nehmen, wenn es keine anderen praktikablen Lösungen gibt ...
hjk
7

Die Kombination der Antwort von @ DarkHeart mit der Umgebungsvariablen BASH_SOURCEscheint den Trick zu machen:

$ 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 Scheint noch einfacher zu sein, wenn ich nur die Anzahl der Elemente im BASH_SOURCEArray zähle :

if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "running directly"; else echo "sourced from $0"; fi
hjk
quelle
1
Anscheinend haben wir gleichzeitig die Variable 'bash_source' gefunden. :)
DarkHeart
@DarkHeart Ich habe meiner Antwort die Verwendung des Zählens der Array-Größe hinzugefügt ... Vielen Dank, dass Sie mich auf diesen Pfad hingewiesen haben! : D
hjk
1

Ich habe gerade die gleiche Art von Bibliotheksskript erstellt, die ähnlich wie BusyBox funktioniert. Darin benutze ich die folgende Funktion, um zu testen, ob es bezogen wird ...

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

Das von Bash gepflegte FUNCNAME-Array ist im Wesentlichen ein Funktionsaufrufstapel. $FUNCNAME(oder ${FUNCNAME[0]}) ist der Name der aktuell ausgeführten Funktion. ${FUNCNAME[1]}ist der Name der aufgerufenen Funktion und so weiter.

Das oberste Element ist ein spezieller Wert für das Skript. Es wird enthalten ...

  • das Wort "Quelle", wenn das Skript bezogen wird
  • das Wort "main", wenn das Skript ausgeführt wird UND der Test innerhalb einer Funktion ausgeführt wird
  • "" (null), wenn das Skript ausgeführt wird UND der Test außerhalb einer Funktion ausgeführt wird, dh ... auf der Ebene des Skripts.

Die obige Funktion funktioniert eigentlich nur, wenn sie auf der Skriptebene aufgerufen wird (was alles ist, was ich brauche). Ein Aufruf aus einer anderen Funktion heraus würde fehlschlagen, da die Array-Artikelnummer falsch wäre. Damit es überall funktioniert, müssen Sie den oberen Bereich des Stapels finden und diesen Wert testen, was komplizierter ist.

Wenn Sie das brauchen, können Sie die Array-Artikelnummer der "Oberseite des Stapels" mit ...

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

${#FUNCNAME[@]}ist die Anzahl der Elemente im Array. Als nullbasiertes Array subtrahieren wir 1, um die letzte Artikelnummer zu erhalten.

Diese drei Funktionen werden zusammen verwendet, um eine Funktionsstapel-Ablaufverfolgung zu erstellen, die der von Python ähnelt, und sie geben Ihnen möglicherweise eine bessere Vorstellung davon, wie dies alles funktioniert ...

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
}

Beachten Sie, dass FUNCNAME, BASH_SOURCE und BASH_LINENO drei von bash verwaltete Arrays sind, als ob sie ein dreidimensionales Array wären.

DocSalvager
quelle
0

Ich möchte nur hinzufügen, dass das Zählen des Arrays unzuverlässig erscheint, und man sollte wahrscheinlich nicht annehmen, dass sourcees verwendet wurde, da die Verwendung eines Punkts ( .) ebenfalls sehr häufig ist (und dem sourceSchlüsselwort vorausgeht ).

Zum Beispiel für ein sourced.shSkript, das nur Folgendes enthält echo $0:


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

Die vorgeschlagenen Vergleichslösungen funktionieren besser.

ka1l
quelle
0

Eine Möglichkeit, die auch bei der Beschaffung aus einer interaktiven Shell funktioniert :

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

Die BASH_LINENOVariable ist auch ein Array mit allen Zeilen, in denen die aufrufende Funktion ausgeführt wurde. Es ist Null, wenn Sie das Skript direkt aufrufen, oder eine Ganzzahl, die einer Zeilennummer entspricht.

Die Variable BASH_ * docs

Borisu
quelle