Combien de coquilles je suis profond?

73

Problème : Trouve combien de coquilles je suis.

Détails : J'ouvre beaucoup le shell de vim. Construire et exécuter et sortir. Parfois, j'oublie et ouvre un autre vim à l'intérieur, puis un autre shell. :(

Je veux savoir combien de coquilles je suis profondément, peut-être même l'avoir sur mon écran de coquille à tout moment. (Je peux gérer cette partie).

Ma solution : analyser l'arbre de processus et rechercher vim et bash / zsh et déterminer la profondeur du processus actuel.

Quelque chose comme ça existe déjà? Je n'ai rien trouvé.

Pranay
la source
27
La $SHLVLvariable (maintenue par plusieurs coquilles) correspond-elle à ce que vous recherchez?
Stéphane Chazelas
1
Pour clarifier, vous n'êtes pas vraiment intéressé par le nombre de shell (directement imbriqués), indiqué par SHLVL, mais si votre shell actuel est un descendant de vim?
Jeff Schaller
14
Cela semble être un peu un problème XY - mon flux de travail est ^ Z pour échapper de l'instance vim dans le shell parent et fgpour revenir en arrière, qui n'a pas ce problème.
Poignée de porte
2
@Doorknob, je le fais aussi. mais je préfère celui-ci, car alors je devrais continuer à vérifier les "emplois". et Il peut y avoir beaucoup de course à la fois sur ma machine. maintenant, ajoutez TMUX avec l'équation. Cela devient complexe et déborde. Si je lance un obus dans Vim, ce sera moins dispersé. (Cependant je finis par faire le bazar et donc la question).
Pranay
3
@Doorknob: Avec tout le respect que je vous dois, cela semble répondre à la question «Comment conduire du point A au point B?» Avec la suggestion «Ne conduisez pas; il suffit de prendre un Uber. »Si l'utilisateur dispose d'un flux de travail impliquant l'édition simultanée de plusieurs fichiers, il vimpeut être plus déroutant de disposer de plusieurs tâches arrêtées parallèlement que de disposer d'une pile de processus imbriqués. Au fait, je préfère avoir plusieurs fenêtres , je peux donc facilement basculer rapidement, mais je n’appellerais pas cela un problème XY simplement parce que je préfère un flux de travail différent.
Scott

Réponses:

45

Quand j'ai lu votre question, ma première pensée a été $SHLVL. Ensuite, j'ai vu que vous vouliez compter les vimniveaux en plus des niveaux de coque. Un moyen simple de le faire est de définir une fonction shell:

vim()  { ( ((SHLVL++)); command vim  "$@");}

Cela augmentera automatiquement et silencieusement SHLVL chaque fois que vous taperez une vimcommande. Vous devrez le faire pour chaque variante de vi/ vimque vous utilisez déjà; par exemple,

vi()   { ( ((SHLVL++)); command vi   "$@");}
view() { ( ((SHLVL++)); command view "$@");}

L'ensemble de parenthèses externe crée un sous-shell, de sorte que la modification manuelle de la valeur de SHLVL ne contamine pas l'environnement de coque actuel (parent). Bien sûr, le commandmot clé est là pour empêcher les fonctions de s’appeler (ce qui entraînerait une boucle de récursion infinie). Et bien sûr, vous devriez mettre ces définitions dans votre .bashrcfichier d'initialisation du shell ou dans un autre.


Il y a une légère inefficacité dans ce qui précède. Dans certains coquillages (bash étant un), si vous dites

( cmd 1 ;  cmd 2 ;;  cmd n )

où est un programme exécutable externe (c.-à-d., pas une commande intégrée), le shell garde un processus supplémentaire traîner, juste pour attendre la fin. Ce n'est (sans doute) pas nécessaire; les avantages et les inconvénients sont discutables. Si cela ne vous dérange pas de garder un peu de mémoire et un emplacement de processus (et de voir un processus shell plus que nécessaire en a ), faites ce qui précède et passez à la section suivante. Idem si vous utilisez un shell qui ne laisse pas traîner le processus supplémentaire. Mais si vous voulez éviter le processus supplémentaire, la première chose à faire est d'essayercmdncmdnps

vim()  { ( ((SHLVL++)); exec vim  "$@");}

La execcommande est là pour empêcher le processus shell supplémentaire de rester en attente.

Mais, il y a un gotcha. Le traitement du shell SHLVLest quelque peu intuitif: au démarrage, le shell vérifie si SHLVLest défini. S'il n'est pas défini (ou défini sur autre chose qu'un nombre), le shell le définit sur 1. S'il est défini (sur un nombre), le shell ajoute 1 à celui-ci.

Mais, par cette logique, si vous dites exec sh, vous SHLVLdevriez monter. Mais ce n'est pas souhaitable, car votre niveau de coque réel n'a pas augmenté. Le shell gère cela en soustrayant un de SHLVL lorsque vous faites un exec:

$ echo "$SHLVL"
1

$ set | grep SHLVL
SHLVL=1

$ env | grep SHLVL
SHLVL=1

$ (env | grep SHLVL)
SHLVL=1

$ (env) | grep SHLVL
SHLVL=1

$ (exec env) | grep SHLVL
SHLVL=0

Alors

vim()  { ( ((SHLVL++)); exec vim  "$@");}

est un lavage; il incrémente SHLVLseulement pour le décrémenter à nouveau. Vous pourriez aussi bien dire vim, sans bénéfice d'une fonction.

Note:
Selon Stéphane Chazelas (qui sait tout) , certains obus sont suffisamment intelligents pour ne pas le faire s’ils se trouvent execdans un sous-shell.

Pour résoudre ce problème, vous feriez

vim()  { ( ((SHLVL+=2)); exec vim  "$@");}

Ensuite, j'ai vu que vous vouliez compter les vimniveaux indépendamment des niveaux de coque. Eh bien, le même truc fonctionne (bien, avec une modification mineure):

vim() { ( ((SHLVL++, VILVL++)); export VILVL; exec vim "$@");}

(et ainsi de suite pour vi, viewetc.) Le exportest nécessaire parce que VILVLn'est pas définie comme une variable d'environnement par défaut. Mais cela n’a pas besoin de faire partie de la fonction; vous pouvez simplement dire en export VILVLtant que commande séparée (dans votre .bashrc). Et, comme indiqué ci-dessus, si le processus de shell supplémentaire ne vous pose pas problème, vous pouvez le faire à la command vimplace exec vimet laisser les choses suivantes SHLVL:

vim() { ( ((VILVL++)); command vim "$@");}

Préférence personnelle:
Vous voudrez peut-être renommer VILVLquelque chose comme VIM_LEVEL. Quand je regarde “ VILVL”, j'ai mal aux yeux; ils ne peuvent pas dire s'il s'agit d'une mauvaise orthographe de «vinyle» ou d'un chiffre romain mal formé.


Si vous utilisez un shell qui ne prend pas en charge SHLVL(par exemple, dash), vous pouvez l'implémenter vous-même, à condition que le shell implémente un fichier de démarrage. Juste faire quelque chose comme

if [ "$SHELL_LEVEL" = "" ]
then
    SHELL_LEVEL=1
else
    SHELL_LEVEL=$(expr "$SHELL_LEVEL" + 1)
fi
export SHELL_LEVEL

dans votre .profilefichier ou fichier applicable. (Vous ne devriez probablement pas utiliser le nom SHLVL, car cela provoquerait le chaos si vous utilisiez un shell compatible SHLVL.)


D'autres réponses ont abordé la question de l'intégration de valeurs de variable d'environnement dans votre invite de shell, je ne vais donc pas le répéter, surtout si vous dites que vous savez déjà comment le faire.

Scott
la source
1
Je suis quelque peu surpris que tant de réponses suggèrent d'exécuter un programme exécutable externe, comme psou pstree, quand vous pouvez le faire avec des fonctions intégrées au shell.
Scott
Cette réponse est parfaite. J'ai marqué ceci comme solution (malheureusement, il n'y a pas encore beaucoup de votes).
Pranay
Votre approche est incroyable et vous utilisez uniquement les primitives, ce qui signifie que l'inclure dans mon .profile / .shellrc ne casse rien. Je tire ceux sur n'importe quelle machine sur laquelle je travaille.
Pranay
1
Notez que dasha une expansion arithmétique. SHELL_LEVEL=$((SHELL_LEVEL + 1))devrait être suffisant même si $ SHELL_LEVEL était précédemment non défini ou vide. Ce n'est que si vous devez être portable sur le shell Bourne que vous devez recourir expr, mais vous devez également le remplacer $(...)par `..`. SHELL_LEVEL=`expr "${SHELL_LEVEL:-0}" + 1`
Stéphane Chazelas
2
@Pranay, il est peu probable que ce soit un problème. Si un attaquant peut injecter une variable d’env arbitraire, des options telles que PATH / LD_PRELOAD sont plus évidentes, mais si des variables non problématiques sont acceptées, comme avec sudo configuré sans reset_env (et on peut forcer un bashscript à lire ~ / .bashrc par faire de stdin une socket par exemple), alors cela peut devenir un problème. C'est beaucoup de "si", mais il y a quelque chose à garder en tête (des données non normalisées dans un contexte arithmétique sont dangereuses)
Stéphane Chazelas
37

Vous pouvez compter autant de temps que nécessaire pour accéder à l'arborescence des processus jusqu'à ce que vous trouviez un responsable de session. Comme avec zshLinux:

lvl() {
  local n=0 pid=$$ buf
  until
    IFS= read -rd '' buf < /proc/$pid/stat
    set -- ${(s: :)buf##*\)}
    ((pid == $4))
  do
    ((n++))
    pid=$2
  done
  echo $n
}

Ou POSIXly (mais moins efficace):

lvl() (
  unset IFS
  pid=$$ n=0
  until
    set -- $(ps -o ppid= -o sid= -p "$pid")
    [ "$pid" -eq "$2" ]
  do
    n=$((n + 1)) pid=$1
  done
  echo "$n"
)

Cela donnerait 0 pour le shell qui a été lancé par votre émulateur de terminal ou getty, et un de plus pour chaque descendant.

Il suffit de le faire une fois au démarrage. Par exemple avec:

PS1="[$(lvl)]$PS1"

dans votre ~/.zshrcou équivalent pour l'avoir dans votre invite.

tcshet plusieurs autres coquilles ( zsh, ksh93, fishet bashau moins) maintiennent une $SHLVLvariable qu'ils incrémenter au démarrage (et décrément avant d' exécuter une autre commande avec exec( à moins que execc'est dans un sous - shell si elles ne sont pas buggé (mais beaucoup sont))). Cela ne fait que suivre la quantité d' imbrication de shell , pas de processus d'imbrication. De plus, le niveau 0 n'est pas garanti pour être le responsable de la session.

Stéphane Chazelas
la source
Ouais .. ceci ou similaire. Je ne souhaitais pas écrire cela moi-même et je n'avais pas l'intention de demander à quelqu'un de l'écrire pour moi. :(. J'espérais une fonctionnalité de vim ou shell ou un plugin régulièrement mis à jour. J'ai cherché mais n'ai rien trouvé.
Pranay
31

Utilisez echo $SHLVL. Utilisez le principe KISS . Selon la complexité de votre programme, cela peut être suffisant.

utilisateur2497
la source
2
Fonctionne pour bash, mais pas pour dash.
agc
SHLVL ne m'aide pas. J'étais au courant et cela a également été abordé lors de la recherche. :) Il y a plus de détails dans la question.
Pranay
@Pranay Êtes-vous certain que vim ne fournit pas cette information?
user2497
@ user2497, je suis un peu. C'est la prémisse de la question. J'ai cherché partout, j'étais au courant de SHLVL aussi. Je voulais -> a) être certain que cela n'existe pas. b) le faire avec un minimum de dépendances / maintenance.
Pranay
16

Une solution potentielle consiste à examiner le résultat de pstree. Lorsqu'elle est exécutée à l'intérieur d'un shell créé de l'intérieur vi, la partie de l'arborescence répertoriée pstreedoit indiquer votre profondeur. Par exemple:

$ pstree <my-user-ID>
...
       ├─gnome-terminal-─┬─bash───vi───sh───vi───sh───pstree
...
John
la source
Oui, c'est ce que j'ai suggéré comme solution (dans la question). Je ne souhaite pas analyser le pstree cependant :(. C'est bon pour le lire manuellement, je pensais écrire un programme pour le faire pour moi et me le faire savoir. Je ne suis pas très enclin à écrire un analyseur si un plugin / outil le fait déjà :).
Pranay
11

Première variante - profondeur de la coque uniquement.

Solution simple pour bash: ajouter aux .bashrcdeux lignes suivantes (ou modifier votre PS1valeur actuelle ):

PS1="${SHLVL} \w\$ "
export PS1

Résultat:

1 ~$ bash
2 ~$ bash
3 ~$ exit
exit
2 ~$ exit
exit
1 ~$

Le nombre au début de la chaîne d'invite sera le niveau du shell.

Deuxième variante, avec les niveaux imbriqués vim et shell.

ajouter ces lignes à la .bashrc

branch=$(pstree -ls $$)
vim_lvl=$(grep -o vim <<< "$branch" | wc -l)
sh_lvl=$(grep -o bash <<< "$branch" | wc -l)
PS1="v:${vim_lvl};s:$((sh_lvl - 1)):\w\$ "
export PS1

Résultat:

v:0;s:1:/etc$ bash
v:0;s:2:/etc$ bash
v:0;s:3:/etc$ vim
##### do ':sh' command in the vim, shell level is increasing by 1
v:1;s:4:/etc$ vim
##### do ':sh' command in the vim, shell level is increasing by 1
v:2;s:5:/etc$ bash
v:2;s:6:/etc$

v: 1 - niveau de profondeur vim
s: 3 - niveau de profondeur de coque

MiniMax
la source
cela me donnera la nidification bash. Cela ne me donnera pas les nids Vim. :)
Pranay
@Pranay Vérifiez la nouvelle solution. Il fait ce que tu veux.
MiniMax
Oui, c'est une bonne solution. Je peux ajouter plus de coquillages et ça marcherait :).
Pranay
8

Dans la question que vous avez mentionné l'analyse de pstree. Voici un moyen relativement simple:

bash-4.3$ pstree -Aals $$ | grep -E '^ *`-((|ba|da|k|c|tc|z)sh|vim?)( |$)'
                  `-bash
                      `-bash --posix
                          `-vi -y
                              `-dash
                                  `-vim testfile.txt
                                      `-tcsh
                                          `-csh
                                              `-sh -
                                                  `-zsh
                                                      `-bash --norc --verbose

Les pstreeoptions:

  • -A- Sortie ASCII pour un filtrage plus facile (dans notre cas, chaque commande est précédée de `-)
  • -a - affiche également les arguments de la commande. En tant qu'effet secondaire, chaque commande est affichée sur une ligne séparée et nous pouvons facilement filtrer le résultat en utilisant grep
  • -l - ne pas tronquer les longues lignes
  • -s- montrer aux parents du processus sélectionné
    (malheureusement pas supporté dans les anciennes versions de pstree)
  • $$ - le processus sélectionné - le PID du shell actuel
Pabouk
la source
Oui, je le faisais assez bien. J'avais aussi quelque chose à compter "bash" et "vim" etc. Je ne voulais simplement pas le maintenir. Il n'est également pas possible d'avoir beaucoup de fonctionnalités personnalisées lorsque vous devez basculer sur de nombreuses machines virtuelles et les développer parfois.
Pranay
3

Cela ne répond pas strictement à la question, mais dans de nombreux cas, il peut être inutile de le faire:

Lorsque vous lancez votre shell pour la première fois, lancez set -o ignoreeof. Ne le mets pas dans ton ~/.bashrc.

Prenez l'habitude de taper Ctrl-D lorsque vous pensez être dans le shell de niveau supérieur et que vous voulez en être sûr.

Si vous n'êtes pas dans le shell de niveau supérieur, Ctrl-D signalera "la fin de l'entrée" au shell actuel et vous reculerez d'un niveau.

Si vous êtes dans le shell de niveau supérieur, vous recevrez un message:

Use "logout" to leave the shell.

Je l’utilise tout le temps pour les sessions SSH chaînées, afin de faciliter le retour à un niveau spécifique de la chaîne SSH. Cela fonctionne aussi bien pour les coquilles imbriquées.

Wildcard
la source
1
Cela aide vraiment et oui, cela éliminera beaucoup de complications :). Je pourrais simplement combiner cela avec la réponse acceptée :)). Défini conditionnellement, je n'aurai peut-être pas à regarder mon invite tout le temps.
Pranay