Home » How many shells deep I am?

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"

$ set | grep SHLVL

$ env | grep SHLVL

$ (env | grep SHLVL)

$ (env) | grep SHLVL

$ (exec env) | grep SHLVL


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.

According to Stéphane Chazelas (who knows everything),
some shells are smart enough not to do this if the exec 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 rename VILVL to something like VIM_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" = "" ]
    SHELL_LEVEL=$(expr "$SHELL_LEVEL" + 1)

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
    IFS= read -rd '' buf < /proc/$pid/stat
    set -- ${(s: :)buf##*)}
    ((pid == $4))
  echo $n

Or POSIXly (but less efficient):

lvl() (
  unset IFS
  pid=$$ n=0
    set -- $(ps -o ppid= -o sid= -p "$pid")
    [ "$pid" -eq "$2" ]
    n=$((n + 1)) pid=$1
  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:


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.

Related Solutions

Joining bash arguments into single string with spaces

[*] I believe that this does what you want. It will put all the arguments in one string, separated by spaces, with single quotes around all: str="'$*'" $* produces all the scripts arguments separated by the first character of $IFS which, by default, is a space....

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every...

How to download package not install it with apt-get command?

Use --download-only: sudo apt-get install --download-only pppoe This will download pppoe and any dependencies you need, and place them in /var/cache/apt/archives. That way a subsequent apt-get install pppoe will be able to complete without any extra downloads....

What defines the maximum size for a command single argument?

Answers Definitely not a bug. The parameter which defines the maximum size for one argument is MAX_ARG_STRLEN. There is no documentation for this parameter other than the comments in binfmts.h: /* * These are the maximum length and maximum number of strings...

Bulk rename, change prefix

I'd say the simplest it to just use the rename command which is common on many Linux distributions. There are two common versions of this command so check its man page to find which one you have: ## rename from Perl (common in Debian systems -- Ubuntu, Mint,...

Output from ls has newlines but displays on a single line. Why?

When you pipe the output, ls acts differently. This fact is hidden away in the info documentation: If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is...

mv: Move file only if destination does not exist

mv -vn file1 file2. This command will do what you want. You can skip -v if you want. -v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved) -n moves only if file2 does not exist....

Is it possible to store and query JSON in SQLite?

SQLite 3.9 introduced a new extension (JSON1) that allows you to easily work with JSON data . Also, it introduced support for indexes on expressions, which (in my understanding) should allow you to define indexes on your JSON data as well. PostgreSQL has some...

Combining tail && journalctl

You could use: journalctl -u service-name -f -f, --follow Show only the most recent journal entries, and continuously print new entries as they are appended to the journal. Here I've added "service-name" to distinguish this answer from others; you substitute...

how can shellshock be exploited over SSH?

One example where this can be exploited is on servers with an authorized_keys forced command. When adding an entry to ~/.ssh/authorized_keys, you can prefix the line with command="foo" to force foo to be run any time that ssh public key is used. With this...

Why doesn’t the tilde (~) expand inside double quotes?

The reason, because inside double quotes, tilde ~ has no special meaning, it's treated as literal. POSIX defines Double-Quotes as: Enclosing characters in double-quotes ( "" ) shall preserve the literal value of all characters within the double-quotes, with the...

What is GNU Info for?

GNU Info was designed to offer documentation that was comprehensive, hyperlinked, and possible to output to multiple formats. Man pages were available, and they were great at providing printed output. However, they were designed such that each man page had a...

Set systemd service to execute after fstab mount

a CIFS network location is mounted via /etc/fstab to /mnt/ on boot-up. No, it is not. Get this right, and the rest falls into place naturally. The mount is handled by a (generated) systemd mount unit that will be named something like mnt-wibble.mount. You can...

Merge two video clips into one, placing them next to each other

To be honest, using the accepted answer resulted in a lot of dropped frames for me. However, using the hstack filter_complex produced perfectly fluid output: ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4 ffmpeg -i input1.mp4 -i input2.mp4...

How portable are /dev/stdin, /dev/stdout and /dev/stderr?

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or...

How can I increase the number of inodes in an ext4 filesystem?

It seems that you have a lot more files than normal expectation. I don't know whether there is a solution to change the inode table size dynamically. I'm afraid that you need to back-up your data, and create new filesystem, and restore your data. To create new...

Why doesn’t cp have a progress bar like wget?

The tradition in unix tools is to display messages only if something goes wrong. I think this is both for design and practical reasons. The design is intended to make it obvious when something goes wrong: you get an error message, and it's not drowned in...