How many shells deep I am?
When I read your question, my first thought was $SHLVL
.
Then I saw that you wanted to count vim
levels
in addition to shell levels.
A simple way to do this is to define a shell function:
vim() { ( ((SHLVL++)); command vim "$@");}
This will automatically and silently increment SHLVL
each time you type a vim
command.
You will need to do this for each variant of vi
/vim
that you ever use; e.g.,
vi() { ( ((SHLVL++)); command vi "$@");}
view() { ( ((SHLVL++)); command view "$@");}
The outer set of parentheses creates a subshell,
so the manual change in the value of SHLVL
doesn’t contaminate the current (parent) shell environment.
Of course the command
keyword is there to prevent the functions
from calling themselves (which would result in an infinite recursion loop).
And of course you should put these definitions
into your .bashrc
or other shell initialization file.
There’s a slight inefficiency in the above. In some shells (bash being one), if you say
(cmd1; cmd2; …; cmdn)
where cmdn
is an external, executable program
(i.e., not a built-in command), the shell keeps an extra process lying around,
just to wait for cmdn
to terminate.
This is (arguably) not necessary;
the advantages and disadvantages are debatable.
If you don’t mind tying up a bit of memory and a process slot
(and to seeing one more shell process than you need when you do a ps
),
then do the above and skip to the next section.
Ditto if you’re using a shell that doesn’t keep the extra process lying around.
But, if you want to avoid the extra process, a first thing to try is
vim() { ( ((SHLVL++)); exec vim "$@");}
The exec
command is there to prevent the extra shell process from lingering.
But, there’s a gotcha.
The shell’s handling of SHLVL
is somewhat intuitive:
When the shell starts, it checks whether SHLVL
is set.
If it’s not set (or set to something other than a number),
the shell sets it to 1.
If it is set (to a number), the shell adds 1 to it.
But, by this logic, if you say exec sh
, your SHLVL
should go up.
But that’s undesirable, because your real shell level hasn’t increased.
The shell handles this by subtracting one from SHLVL
when you do an 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
So
vim() { ( ((SHLVL++)); exec vim "$@");}
is a wash; it increments SHLVL
only to decrement it again.
You might as well just say vim
, without benefit of a function.
Note:
According to Stéphane Chazelas (who knows everything), some shells are smart enough not to do this if theexec
is in a subshell.
To fix this, you would do
vim() { ( ((SHLVL+=2)); exec vim "$@");}
Then I saw that you wanted to count vim
levels
independently of shell levels.
Well, the exact same trick works (well, with a minor modification):
vim() { ( ((SHLVL++, VILVL++)); export VILVL; exec vim "$@");}
(and so on for vi
, view
, etc.)
The export
is necessary
because VILVL
isn’t defined as an environment variable by default.
But it doesn’t need to be part of the function;
you can just say export VILVL
as a separate command (in your .bashrc
).
And, as discussed above, if the extra shell process isn’t an issue for you,
you can do command vim
instead of exec vim
, and leave SHLVL
alone:
vim() { ( ((VILVL++)); command vim "$@");}
Personal Preference:
You may want to renameVILVL
to something likeVIM_LEVEL
. When I look at “VILVL
”, my eyes hurt; they can’t tell whether it’s a misspelling of “vinyl” or a malformed Roman numeral.
If you are using a shell that doesn’t support SHLVL
(e.g., dash),
you can implement it yourself as long as the shell implements a startup file.
Just do something like
if [ "$SHELL_LEVEL" = "" ]
then
SHELL_LEVEL=1
else
SHELL_LEVEL=$(expr "$SHELL_LEVEL" + 1)
fi
export SHELL_LEVEL
in your .profile
or applicable file.
(You should probably not use the name SHLVL
, as that will cause chaos
if you ever start using a shell that supports SHLVL
.)
Other answers have addressed the issue of embedding environment variable value(s) into your shell prompt, so I won’t repeat that, especially you say you already know how to do it.
You could count as many time you need to go up the process tree until you find a session leader. Like with zsh
on 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
}
Or POSIXly (but less efficient):
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"
)
That would give 0 for the shell that was started by your terminal emulator or getty and one more for each descendant.
You only need to do that once on startup. For instance with:
PS1="[$(lvl)]$PS1"
in your ~/.zshrc
or equivalent to have it in your prompt.
tcsh
and several other shells (zsh
, ksh93
, fish
and bash
at least) maintain a $SHLVL
variable which they increment on startup (and decrement before running another command with exec
(unless that exec
is in a subshell if they're not buggy (but many are))). That only tracks the amount of shell nesting though, not process nesting. Also level 0 is not guaranteed to be the session leader.
Use echo $SHLVL
. Use the KISS principle. Depending on your program's complexity, this may be enough.