Wie kann ich erkennen, ob ich in einer Subshell bin?

24

Ich versuche, eine Funktion zu schreiben, die die Funktionalität des exiteingebauten Moduls ersetzt, um zu verhindern, dass ich das Terminal verlasse.

Ich habe versucht, die SHLVLUmgebungsvariable zu verwenden, aber sie scheint sich innerhalb von Subshells nicht zu ändern:

$ echo $SHLVL
1
$ ( echo $SHLVL )
1
$ bash -c 'echo $SHLVL'
2

Meine Funktion ist wie folgt:

exit () {
    if [[ $SHLVL -eq 1 ]]; then
        printf '%s\n' "Nice try!" >&2
    else
        command exit
    fi
}

Dies erlaubt mir jedoch nicht, exitinnerhalb von Subshells zu arbeiten:

$ exit
Nice try!
$ (exit)
Nice try!

Was ist eine gute Methode, um festzustellen, ob ich in einer Unterschale bin oder nicht?

Jesse_b
quelle
1
Das liegt daran , von diesem . $ SHLVL ist 1, weil Sie sich immer noch auf Shell-Ebene 1 befinden, obwohl der Befehl echo $ SHLVL in einer "Subshell" ausgeführt wird. Gemäß diesem Beitrag erben mit Klammern erzeugte Subshells (...)alle Eigenschaften des übergeordneten Prozesses. Die Antworten sind robustere Lösungen zur Bestimmung Ihres Shell-Levels.
Kemotep
1
Mögliches Duplikat von Wie kann ich die PID einer Subshell erhalten?
Mosvy
5
@mosvy Ich denke, das ist eine andere Frage. zB würde die BASH_SUBSHELLAntwort (auch wenn sie umstritten ist) nicht auf diese Frage zutreffen.
Sparhawk
2
Sah den Titel auf HNQ und dachte, dies sei eine Frage der Quantenmechanik ...
Mehrdad

Antworten:

43

In bash können Sie vergleichen $BASHPIDzu$$

$ ( if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi )
subshell
$   if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi
not subshell

Wenn Sie nicht in der Bash- $$Phase sind, sollten Sie in einer Subshell gleich bleiben, sodass Sie eine andere Möglichkeit benötigen, um Ihre eigentliche Prozess-ID zu erhalten.

Eine Möglichkeit, um Ihre tatsächliche PID zu erhalten, ist sh -c 'echo $PPID'. Wenn Sie das einfach in eine Ebene legen, ( … )scheint es nicht zu funktionieren, da Ihre Schale die Gabel wegoptimiert hat. Versuchen Sie es mit zusätzlichen No-Op-Befehlen ( : ; sh -c 'echo $PPID'; : ), um die Subshell für zu kompliziert zu halten, um sie zu optimieren. Die Gutschrift für diesen Ansatz geht an John1024 bei Stack Overflow .

derobert
quelle
Sie könnten sich ändern wollen das  (sh -c 'echo $PPID'; : )- siehe meinen Kommentar auf John1024 Antwort .
G-Man sagt, dass Monica
@ G-Man Nun, das war nur, um es zu testen (da es im tatsächlichen Gebrauch etwas komplizierter wäre) ... aber ja, wäre am besten, wenn der Test in allen Shells funktioniert. Also habe ich vorher und nachher ein No-Op gesetzt, das hoffentlich alles regelt.
Derobert
38

Wie wäre es BASH_SUBSHELL?

BASH_SUBSHELL Wird
      innerhalb jeder Subshell oder Subshell-Umgebung um eins erhöht, wenn die Shell
      in dieser Umgebung mit der Ausführung beginnt. Der Anfangswert ist 0.

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1
Freddy
quelle
16
Es wäre ein bequemer Befehl im Film Inception gewesen.
Eric Duminil
In Inception ist es wahrscheinlich $ SHLVL
Granny Aching
19

[dies hätte ein Kommentar sein sollen, aber meine Kommentare werden in der Regel von Moderatoren gelöscht, daher bleibt dies eine Antwort, die ich als Referenz verwenden könnte, auch wenn sie gelöscht werden.]

Die Verwendung BASH_SUBSHELList völlig unzuverlässig, da sie nur in einigen Subshells auf 1 gesetzt wird, nicht in allen Subshells.

$ (echo $BASH_SUBSHELL)
1
$ echo $BASH_SUBSHELL | cat
0

Bevor Sie behaupten, dass der Subprozess, in dem ein Pipeline-Befehl ausgeführt wird, keine wirklich echte Subshell ist, sollten Sie folgendes man bashSnippet betrachten:

Jeder Befehl in einer Pipeline wird als separater Prozess (dh in einer Subshell) ausgeführt.

und die praktischen Implikationen - es ist wichtig, ob ein Skriptfragment in einem Unterprozess ausgeführt wird oder nicht, und keine terminologische Auseinandersetzung.

Die einzige Lösung, wie bereits in den Antworten auf diese Frage erläutert , besteht darin, zu prüfen, ob sie $BASHPIDgleich $$oder tragbar, aber viel weniger effizient sind:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi
Mosvy
quelle
11
Nit: BASH_SUBSHELList ziemlich zuverlässig eingestellt, aber es ist zweifelhaft, ob der Wert richtig eingestellt wird. Beachten Sie, was in den Dokumenten steht : "Inkrementiert um eins in jeder Subshell- oder Subshell-Umgebung, wenn die Shell in dieser Umgebung ausgeführt wird. " Ich glaube, dass im Pipe-Beispiel die Ausführung von Bash in dieser Subshell noch nicht begonnen hat, wenn die Variable erweitert wird. Sie können vergleichen echo $BASH_VERSIONmit declare -p BASH_VERSION- letzteres sollte zuverlässig Ausgang 1 mit Rohren, Hintergrundjobs etc.
muru
6
Sogar say eval 'echo $BASH_SUBSHELL $BASHPID' | catgibt 1 für aus BASH_SUBSHELL, da die Variable nach dem Start der Ausführung erweitert wird.
muru
4
Alle diese Argumente sollten auch für die Substitution von Prozessen und Befehlen gelten, aber nur die Pipelines sind unterschiedlich. Wenn man sich den Code ansieht, wird das Inkrementieren im Fall von Vordergrund- Pipelines subshell_levelwirklich aufgeschoben , was wahrscheinlich einen Grund hat, den ich aber nicht ausmachen kann ;-)
mosvy
2
Du hast recht. Chet scheint das ausdrücklich so zu wollen. lists.gnu.org/archive/html/bug-bash/2015-06/msg00050.html : "BASH_SUBSHELL misst (...) Subshells, keine Pipeline-Elemente." lists.gnu.org/archive/html/bug-bash/2015-06/msg00054.html : "Ich werde darüber nachdenken, ob ich den Status Quo dokumentieren oder die Definition von" subshell "erweitern soll, die $ BASH_SUBSHELL widerspiegelt. "
muru
2
@JoL Sie sind falsch, die Erweiterung erfolgt auch im separaten Prozess. Lesen Sie die Links und Beispiele aus dieser Diskussion oben. oder einfach mal probieren mit echo $$ $BASHPID $BASH_SUBSHELL | cat.
Mosvy