Problem: Find how many shells deep I am.
Details:
I open the shell from vim a lot. Build and run and exit. Sometimes I forget and open another vim inside and then yet another shell. đ
I want to know how many shells deep I am, perhaps even have it on my shell screen at all times. (I can manage that part).
My solution: Parse the process tree and look for vim and bash/zsh and figure out the current processâs depth within it.
Does something like that already exist? I could not find anything.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & Iâll get back to you as soon as possible.
Method 1
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 "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="113551">[email protected]</a>");}
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 "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="042044">[email protected]</a>");}
view() { ( ((SHLVL++)); command view "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a581e5">[email protected]</a>");}
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 "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="597d19">[email protected]</a>");}
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 "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="cbef8b">[email protected]</a>");}
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 theexecis in a subshell.
To fix this, you would do
vim() { ( ((SHLVL+=2)); exec vim "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="052145">[email protected]</a>");}
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 "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="765236">[email protected]</a>");}
(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 "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="fcd8bc">[email protected]</a>");}
Personal Preference:
You may want to renameVILVLto 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.
Method 2
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.
Method 3
Use echo $SHLVL. Use the KISS principle. Depending on your programâs complexity, this may be enough.
Method 4
One potential solution is to look at the output of pstree. When run inside a shell that was spawned from within vi, the part of the tree tree that lists pstree should show you how deep you are. For example:
$ pstree <my-user-ID>
...
ââgnome-terminal-ââŹâbashâââviâââshâââviâââshâââpstree
...
Method 5
First variant â shell depth only.
Simple solution for bash: add to the .bashrc next two lines (or change your current PS1 value):
PS1="${SHLVL} w$ "
export PS1
Result:
1 ~$ bash 2 ~$ bash 3 ~$ exit exit 2 ~$ exit exit 1 ~$
Number at the beginning of prompt string will be denote shell level.
Second variant, with nested vim and shell levels both.
add this lines to the .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
Result:
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 â vim depth level
s:3 â shell depth level
Method 6
In the question you mentioned parsing of pstree. Here is a relatively simple way:
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
The pstree options:
-Aâ ASCII output for easier filtering (in our case every command is preceded by`-)-aâ show also command arguments, as a side-effect every command is shown on a separate line and we can easily filter the output usinggrep-lâ do not truncate long lines-sâ show parents of the selected process
(unfortunately not supported in old versions ofpstree)$$â the selected process â the PID of the current shell
Method 7
This doesnât strictly answer the question but in many cases may make it unnecessary to do so:
When you first launch your shell, run set -o ignoreeof. Donât put it in your ~/.bashrc.
Make it a habit to type Ctrl-D when you think you are in the top level shell and want to be sure.
If youâre not in the top level shell, Ctrl-D will signal âend of inputâ to the current shell and you will drop back one level.
If you are in the top level shell, you will get a message:
Use "logout" to leave the shell.
I use this all the time for chained SSH sessions, to make it easy to drop back to a specific level of the SSH chain. It works for nested shells just as well.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0