web-dev-qa-db-fra.com

Combien de coquilles suis-je profondément?

Problème: Trouvez combien d'obus je suis profond.

Détails: J'ouvre beaucoup le Shell depuis vim. Construisez, exécutez et quittez. Parfois, j'oublie et ouvre un autre vim à l'intérieur, puis encore un autre Shell. :(

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

Ma solution: analyser l'arborescence des processus et rechercher vim et bash/zsh et déterminer la profondeur du processus en cours.

Est-ce que quelque chose comme ça existe déjà? Je n'ai rien trouvé.

74
Pranay

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

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

Cela incrémentera automatiquement et silencieusement SHLVL chaque fois que vous tapez une commande vim. Vous devrez le faire pour chaque variante de vi/vim que vous utilisez jamais; par exemple.,

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

Le jeu 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 Shell (parent) actuel. Bien sûr, le mot clé command 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 devez mettre ces définitions dans votre .bashrc ou un autre fichier d'initialisation Shell.


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

(cmd1;cmd2; …;cmdn)

cmdn est un programme exécutable externe (c'est-à-dire, pas une commande intégrée), le Shell garde un processus supplémentaire qui traîne, juste pour attendre cmdn Terminer. Ce n'est (sans doute) pas nécessaire; les avantages et les inconvénients sont discutables. Si cela ne vous dérange pas de lier un peu de mémoire et un emplacement de processus (et de voir un processus Shell de plus que ce dont vous avez besoin lorsque vous faites un ps), faites ce qui précède et passez à la section suivante. Idem si vous utilisez un Shell qui ne fait pas traîner le processus supplémentaire. Mais, si vous voulez éviter le processus supplémentaire, une première chose à essayer est

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

La commande exec est là pour empêcher le processus Shell supplémentaire de persister.

Mais il y a un piège. La gestion par Shell de SHLVL est quelque peu intuitive: lorsque le Shell démarre, il vérifie si SHLVL est 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 lui ajoute 1.

Mais, par cette logique, si vous dites exec sh, votre SHLVL devrait augmenter. Mais ce n'est pas souhaitable, car votre véritable niveau Shell n'a pas augmenté. Le Shell gère cela en en soustrayant un deSHLVL 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

Donc

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

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

Remarque:
Selon Stéphane Chazelas (qui sait tout) , certains obus sont assez intelligents pas pour le faire si le exec est en 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 niveaux de vim indépendamment de les niveaux Shell. Eh bien, exactement la même astuce fonctionne (enfin, avec une modification mineure):

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

(et ainsi de suite pour vi, view, etc.) Le export est nécessaire car VILVL n'est pas défini comme variable d'environnement par défaut. Mais il n'a pas besoin de faire partie de la fonction; vous pouvez simplement dire export VILVL en tant que commande distincte (dans votre .bashrc). Et, comme indiqué ci-dessus, si le processus supplémentaire de Shell n'est pas un problème pour vous, vous pouvez faire command vim au lieu de exec vim, et laissez SHLVL tranquille:

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

Préférence personnelle:
Vous voudrez peut-être renommer VILVL en quelque chose comme VIM_LEVEL. Quand je regarde "VILVL", mes yeux me font mal; ils ne peuvent pas dire s'il s'agit d'une faute d'orthographe de "vinyle" ou d'un chiffre romain mal formé.


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

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

dans ton .profile ou fichier applicable. (Vous ne devriez probablement pas utiliser le nom SHLVL, car cela causera le chaos si vous commencez à utiliser un shell qui prend en charge SHLVL.)


D'autres réponses ont abordé le problème de l'incorporation de valeurs de variables d'environnement dans votre invite de shell, donc je ne le répéterai pas, surtout vous dites que vous savez déjà comment le faire.

46
Scott

Vous pouvez compter autant de fois que vous avez besoin pour remonter l'arborescence des processus jusqu'à ce que vous trouviez un chef de session. Comme avec zsh sous Linux:

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 POSIX (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é démarré par votre émulateur de terminal ou getty et un de plus pour chaque descendant.

Vous ne devez le faire qu'une fois au démarrage. Par exemple avec:

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

dans ton ~/.zshrc ou équivalent pour l'avoir dans votre invite.

tcsh et plusieurs autres shells (zsh, ksh93, fish et bash au moins) maintiennent un $SHLVL variable qu'ils incrémentent au démarrage (et décrémentent avant d'exécuter une autre commande avec exec (sauf si exec est dans un sous-shell s'ils ne sont pas bogués (mais beaucoup le sont))). Cela ne fait que suivre la quantité d'imbrication Shell, pas d'imbrication de processus. De plus, le niveau 0 n'est pas garanti pour être le chef de session.

37
Stéphane Chazelas

Utilisation echo $SHLVL. Utilisez le principe KISS . Selon la complexité de votre programme, cela peut suffire.

31
user2497

Une solution potentielle consiste à regarder la sortie de pstree. Lorsqu'elle est exécutée à l'intérieur d'un shell qui a été généré à partir de vi, la partie de l'arborescence qui répertorie pstree doit vous montrer à quelle profondeur vous êtes. Par exemple:

$ pstree <my-user-ID>
...
       ├─gnome-terminal-─┬─bash───vi───sh───vi───sh───pstree
...
16
John

Première variante - Profondeur coque uniquement.

Solution simple pour bash: ajoutez à la .bashrc les deux lignes suivantes (ou modifiez votre PS1 valeur):

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 Shell.

Deuxième variante, avec les niveaux imbriqués vim et Shell à la fois.

ajoutez ces lignes au .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

11
MiniMax

Dans la question, 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 options pstree:

  • -A - ASCII pour un filtrage plus facile (dans notre cas, chaque commande est précédée de `-)
  • -a - affiche également les arguments de commande, comme effet secondaire, chaque commande est affichée sur une ligne distincte et nous pouvons facilement filtrer la sortie à l'aide de grep
  • -l - ne tronque pas les longues lignes
  • -s - affiche les parents du processus sélectionné
    (malheureusement non pris en charge dans les anciennes versions de pstree)
  • $$ - le processus sélectionné - le PID du shell actuel
8
pabouk

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, exécutez set -o ignoreeof. Ne pas mettez-le dans votre ~/.bashrc.

Prenez l'habitude de taper Ctrl-D lorsque vous pensez que vous êtes 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 "fin d'entrée" au shell actuel et vous reculerez d'un niveau .

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

Use "logout" to leave the Shell.

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

3
Wildcard