How do I know how many sub-shells deep I am?
Sometimes I do things such as starting a sub-shell from vim with :sh
. How do I know if I'm in a sub-shell where exit
will just return me out one level, vs. being in the outermost shell where exit
will log me out or close my session.
Is there some kind of Inception totem I can spin or something to know how many levels deep I am?
Solution 1:
You can use the command pstree
(that comes by default with Ubuntu). Here is example - currently I'm having only one open terminal window on WSL:
User@Wsl:~$ pstree
init─┬─init───bash───pstree
└─{init}
User@Wsl:~$ bash
User@Wsl:~$ sh
$ bash
User@Wsl:~$ pstree
init─┬─init───bash───bash───sh───bash───pstree
└─{init}
Within an actual Linux/Ubuntu environment the process tree will be more complicated. We can filter the tree by the option -s
that will show the parents of a selected process. So our command could be pstree -s $$
, where $$
is an environment variable that contains the current PID:
User@Ubuntu:~$ pstree -s $$
systemd──lightdm──lightdm──upstart──gnome-terminal-──bash──pstree
User@Ubuntu:~$ bash
User@Ubuntu:~$ sh
$ bash
User@Ubuntu:~$ pstree -s $$
systemd──lightdm──lightdm──upstart──gnome-terminal-──bash──bash──sh──bash──pstree
References:
- SuperUser: How to get parent PID of a given process in GNU/Linux from command line?
- HowtoForge: Linux pstree Command Tutorial for Beginners
Add indicator to the shell's prompt: Based on the @waltinator's idea, in order to have a counter in the front of the prompt for several different shells when the level is deeper than one, I've added the lines, shown below the demo, at the bottom of the relevant run commands (~/.*rc
) files.
I've made tests on WSL, Ubuntu 16.04, Ubuntu 18.04 (server/desktop), Ubuntu 19.04, within gnome-terminal, tty and ssh session. Here is how this works:
The limitation is that: the counter works only for 13-14 levels of depth, depending on the OS. I do not intend to investigate the reasons :)
-
bash
>.bashrc
:DEPTH=$(($(pstree -s $$ | sed -r 's/-+/\n/g' | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh)\>') - 1)) if (( DEPTH > 1 )); then PS1=$DEPTH:$PS1; fi
-
csh
andtcsh
>.cshrc
:@ DEPTH = `pstree -s $$ | sed -r 's/-+/\n/g' | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh)\>'` - 0 if ( $DEPTH > 1 ) then; set prompt="$DEPTH":"$prompt"; endif
-
zsh
>.zshrc
:DEPTH=$(($(pstree -s $$ | sed -r 's/-+/\n/g' | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh)\>') - 1)) if (( DEPTH > 1 )); then PROMPT=$DEPTH:$PROMPT; fi
-
ksh
>.kshrc
:DEPTH=$(($(pstree -s $$ | sed -r 's/\-+/\n/g' | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh)\>') - 0)) if (( DEPTH > 1 )); then PS1="$DEPTH":"$PS1"'$ '; fi
-
sh
that is actuallydash
on Ubuntu - here the things are little bit complicated and wired (read the references below for more information):-
Edit the
~/.profile
file and add the following line at the bottom:ENV=$HOME/.shrc; export ENV
-
Create the file
~/.shrc
with the following content, noteksh
also reads the$ENV
:#!/bin/dash DEPTH=$(pstree -s $$ | sed -r 's/-+/\n/g' | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh)\>') if [ "$0" != 'ksh' ]; then DEPTH=$((DEPTH - 1)); fi if [ "$DEPTH" -gt 1 ]; then export PS1='$DEPTH:\$ '; fi
-
References:
- The previous version of the answer, where the counter works only in Bash
- Unix&Linux: Is there an invoked file for dash and sh as non login shells in /etc directory?
- Ubuntu Manuals: Dash (read the BUGS section)
- UnixWare 7 Documentation: The Korn shell
.profile
and.kshrc
Create a command that will output the depth: Another option is to create shell command that will output the depth. For this purpose create the executable file /usr/local/bin/depth
(thus it should be accessible system wide):
sudo touch /usr/local/bin/depth
sudo chmod +x /usr/local/bin/depth
Edit the file with your favorite editor and add the following lines as its content:
#!/bin/bash
SHELLS='(bash|zsh|sh|dash|ksh|csh|tcsh)'
DEPTH=$(pstree -s $$ | sed -r 's/-+/\n/g' | grep -Ec "\<$SHELLS\>")
if [[ $@ =~ -v ]]
then
pstree -s $$ | sed -r 's/-+/\n/g' | grep -E "\<$SHELLS\>" | cat -n
fi
echo "DEPTH: $DEPTH"
[[ $DEPTH -gt 1 ]] && exit 0 || exit 1
The above script has two options -v
or --verbose
that will output a list of the involved shells. And the another option that will check whether the depth is greater than one and based on this will return exit 0
or exit 1
, so you can use it in this way depth && exit
. Here are few examples of usage:
User@Ubuntu:~$ depth # we are at the 1st level - bash
DEPTH: 1
User@Ubuntu:~$ sh
$ csh # we are at the 2nd level - dash
Ubuntu:~% depth # we are at the 3rd level - csh
DEPTH: 3
Ubuntu:~% ksh
$ depth -v # we are at the 4th level - ksh
1 bash
2 sh
3 csh
4 ksh
DEPTH: 4
$ depth && exit # exit to the 3rd level - csh
DEPTH: 4
Ubuntu:~% depth && exit # exit to the 2nd level - dash
DEPTH: 3
exit
$ depth && exit # exit to the 1st level - bash
DEPTH: 2
User@Ubuntu:~$ depth && exit # stay at the 1st level - bash
DEPTH: 1
User@Ubuntu:~$ depth && exit # stay at the 1st level - bash
DEPTH: 1
Comparison by the other solutions: I spent some additional time to find out some weaknesses of the approaches provided here. I was able to imagine the following two cases (the capital letters are needed for better syntax highlighting):
-
When
su
orsudo -i
are involved:User@Ubuntu:~$ ps | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh|su|sudo)\>' 1 User@Ubuntu:~$ echo $SHLVL 1 User@Ubuntu:~$ depth DEPTH: 1 User@Ubuntu:~$ su spas Password: Spas@Ubuntu:~$ ps | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh|su|sudo)\>' 1 Spas@Ubuntu:~$ echo $SHLVL 2 Spas@Ubuntu:~$ depth DEPTH: 2 Spas@Ubuntu:~$ sudo -i [sudo] password for spas: Root@Ubuntu:~# ps | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh|su|sudo)\>' 3 Root@Ubuntu:~# echo $SHLVL 1 Root@Ubuntu:~# depth DEPTH: 3
-
When there a background process is launched:
User@Ubuntu:~$ bash User@Ubuntu:~$ ps | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh)\>' 2 User@Ubuntu:~$ echo $SHLVL 2 User@Ubuntu:~$ depth DEPTH: 2 User@Ubuntu:~$ while true; do sleep 10; done & [1] 10886 User@Ubuntu:~$ ps | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh)\>' 3 User@Ubuntu:~$ echo $SHLVL 2 User@Ubuntu:~$ depth DEPTH: 2 # Note: $SHLVL is not supported only by sh/dash. # It works with all other tested shells: bash, zsh, csh, tcsh, ksh User@Ubuntu:~$ sh $ ps | grep -Ec '\<(bash|zsh|sh|dash|ksh|csh|tcsh)\>' 4 $ echo $SHLVL 2 $ depth DEPTH: 3
Solution 2:
Check the value of the SHLVL
shell variable:
echo $SHLVL
Quoting from bash
's manual page:
SHLVL Incremented by one each time an instance of bash is started.
It is also supported by zsh
.
Solution 3:
In my .bashrc
, I use $SHLVL
to adjust $PS1
, by appending "+
" signs to my $SUBSHELL
variable:
...
# set a variable to reflect SHLVL > 1 (Ubuntu 12.04)
if [[ $SHLVL -gt 1 ]] ; then
export SUBSHELL="${SUBSHELL:+$SUBSHELL}+"
else
export SUBSHELL=""
fi
...
if [[ "$color_prompt" = yes ]]; then
# chroot? Depth green user@host nocolor : green $PWD red (status) off $ or # space
PS1='${debian_chroot:+($debian_chroot)}${SUBSHELL}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[1;31m\]($?)\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}${SUBSHELL}\u@\h:\w\$ '
fi
...
Then, I can see how deep I am:
walt@bat:~(1)$ ed foo
263
!bash
+walt@bat:~(0)$ bash
++walt@bat:~(0)$ bash
+++walt@bat:~(0)$ exit
exit
++walt@bat:~(0)$ exit
exit
+walt@bat:~(0)$ exit
exit
!
q
walt@bat:~(0)$
Solution 4:
awk:
# Count the occurrence of (sh)ells.
DEPTH_REGEX='^(ash|bash|busybox|csh|dash|fish|mksh|sh|tcsh|zsh)$'
DEPTH=$(/bin/ps -s $(/bin/ps -p $$ -osid --no-headers) -ocomm --no-headers | \
awk -v R=$DEPTH_REGEX '{for (A=1; A<=(NR-2); A++) {if ($A ~ R) {B++}}} END {print B}')
pgrep:
DEPTH=$(/usr/bin/pgrep -c -s $(/bin/ps -p $$ -osid --no-headers) '^(ash|bash|busybox|csh|dash|fish|mksh|sh|tcsh|zsh)$')
You can place one of the two versions in a file and use source to make $DEPTH available.
# Set 256 colors in terminal.
if [ -x /usr/bin/tput ] && [ "$(SHELL=/bin/sh tput colors)" -ge 8 ]; then
export TERM="xterm-256color"
fi
# change these if you don't dig my colors!
NM="\[\033[0;1;37m\]" #means no background and white lines
HI="\[\033[0;37m\]" #change this for letter colors
SI="\[\033[38;5;202m\]" #this is for the current directory
NI="\[\033[0;1;30m\]" #for @ symbol
IN="\[\033[0m\]"
# Count the occurrence of (sh)ells.
source /usr/share/shell-depth/depth
PS1="${NM}[${HI}\u${NI}@${HI}\h ${SI}\w${NM} \A](${HI}${DEPTH}${NM}): ${IN}"
Solution 5:
You can simply use ps
without any additional arguments to see the whole shell stack (including the current one). It will be also showing all the background jobs you've started as well as ps
itself, but it can give you a rough estimate of how deep you are.