Comment puis-je détecter si je suis dans un sous-shell?

24

J'essaie d'écrire une fonction pour remplacer la fonctionnalité du exitbuiltin pour m'empêcher de quitter le terminal.

J'ai essayé d'utiliser la SHLVLvariable d'environnement mais elle ne semble pas changer dans les sous-coquilles:

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

Ma fonction est la suivante:

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

Cela ne me permettra pas d'utiliser exitdans les sous-coquilles cependant:

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

Quelle est la bonne méthode pour détecter si je suis ou non dans un sous-shell?

Jesse_b
la source
1
C'est à cause de cela . $ SHLVL vaut 1 car vous êtes toujours au niveau 1 du shell même si la commande echo $ SHLVL est exécutée dans un "sous-shell". Selon ce post, les sous-coquilles générées avec des parenthèses (...)héritent de toutes les propriétés du processus parent. Les réponses fournies sont des solutions plus robustes pour déterminer votre niveau de shell.
kemotep
5
@mosvy J'ai l'impression que c'est une question différente. Par exemple, la BASH_SUBSHELLréponse (même controversée) ne s'appliquerait pas à cette question.
Sparhawk
2
J'ai vu le titre sur HNQ et j'ai pensé que c'était une question de mécanique quantique ...
Mehrdad

Réponses:

43

En bash, vous pouvez comparer $BASHPIDà$$

$ ( 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

Si vous n'êtes pas dans bash, $$devrait rester le même dans un sous-shell, vous auriez donc besoin d'une autre façon d'obtenir votre ID de processus réel.

Une façon d'obtenir votre pid réel est sh -c 'echo $PPID'. Si vous venez de mettre cela en clair, ( … )cela peut sembler ne pas fonctionner, car votre coque a optimisé la fourche. Essayez des commandes supplémentaires sans opération ( : ; sh -c 'echo $PPID'; : )pour lui faire croire que le sous-shell est trop compliqué pour être optimisé. Le crédit va à John1024 sur Stack Overflow pour cette approche.

derobert
la source
Vous voudrez peut-être changer cela pour  (sh -c 'echo $PPID'; : )- voir mon commentaire sur la réponse de John1024 .
G-Man dit `` Réinstalle Monica '' le
@ G-Man Eh bien, c'était juste pour le tester (car en utilisation réelle, ce serait d'une manière beaucoup plus compliquée) ... mais oui, ce serait mieux si le test fonctionnait dans tous les shells. J'ai donc mis un no-op avant et après, qui, espérons-le, gérera tout.
derobert
38

Et alors BASH_SUBSHELL?

BASH_SUBSHELL
      Incrémenté de un dans chaque environnement de sous-shell ou de sous-shell lorsque le shell
      commence à s'exécuter dans cet environnement. La valeur initiale est 0.

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1
Freddy
la source
16
Cela aurait été une commande pratique dans le film Inception.
Eric Duminil
In Inception c'est probablement $ SHLVL
Granny Aching
19

[cela aurait dû être un commentaire, mais mes commentaires ont tendance à être supprimés par les modérateurs, donc cela restera une réponse que je pourrais utiliser comme référence même s'il est supprimé]

L'utilisation BASH_SUBSHELLest complètement peu fiable car elle n'est définie que sur 1 dans certains sous-coquilles, pas dans toutes les sous-coquilles.

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

Avant de prétendre que le sous-processus dans lequel une commande de pipeline est exécutée n'est pas un sous-shell vraiment réel, considérez cet man bashextrait:

Chaque commande d'un pipeline est exécutée comme un processus distinct (c'est-à-dire dans un sous-shell).

et les implications pratiques - c'est si un fragment de script est exécuté ou non sous-processus qui est essentiel, pas un petit problème de terminologie.

La seule solution, comme déjà expliqué dans les réponses à cette question, est de vérifier si $BASHPIDégal $$ou, de manière portative mais beaucoup moins efficace:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi
mosvy
la source
11
Nit: BASH_SUBSHELLest défini de manière assez fiable, mais obtenir sa valeur correctement est incertain. Notez ce que les documents disent: "Incrémenté d'un dans chaque environnement de sous-shell ou de sous-shell lorsque le shell commence à s'exécuter dans cet environnement. " Je pense que dans l'exemple de canal, bash n'a pas encore commencé à s'exécuter dans ce sous-shell lorsque la variable est développée. Vous pouvez comparer echo $BASH_VERSIONavec declare -p BASH_VERSION- ce dernier devrait afficher de manière fiable 1 avec des tuyaux, des travaux d'arrière-plan, etc.
muru
6
Même dire, eval 'echo $BASH_SUBSHELL $BASHPID' | cataffichera 1 pour BASH_SUBSHELL, car la variable est développée après le démarrage de l'exécution.
muru
4
tous ces arguments devraient également s'appliquer à la substitution de processus et de commandes, aux processus bg, mais ce ne sont que les pipelines qui sont différents. En regardant le code, l'incrémentation subshell_levelest vraiment différée dans le cas des pipelines de premier plan , ce qui a probablement une raison, mais que je ne peux pas distinguer ;-)
mosvy
2
Tu as raison. Il semble que Chet le souhaite explicitement de cette façon. lists.gnu.org/archive/html/bug-bash/2015-06/msg00050.html : "BASH_SUBSHELL mesure (...) les sous-coquilles, pas les éléments de pipeline." lists.gnu.org/archive/html/bug-bash/2015-06/msg00054.html : "Je vais me demander si je devrais documenter le statu quo ou étendre la définition de" sous-shell "reflétée par $ BASH_SUBSHELL. "
muru
2
@JoL vous vous trompez, l'expansion se produit également dans le processus séparé, veuillez lire les liens et les exemples de cette discussion ci-dessus; ou essayez simplement avec echo $$ $BASHPID $BASH_SUBSHELL | cat.
mosvy