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é.
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)
où 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 leexec
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 renommerVILVL
en quelque chose commeVIM_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.
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.
Utilisation echo $SHLVL
. Utilisez le principe KISS . Selon la complexité de votre programme, cela peut suffire.
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
...
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.
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
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épstree
)$$
- le processus sélectionné - le PID du shell actuelCela 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.