How can I detect if I’m in a subshell?

I’m trying to write a function to replace the functionality of the exit builtin to prevent myself from exiting the terminal.

I have attempted to use the SHLVL environment variable but it doesn’t seem to change within subshells:

$ echo $SHLVL
1
$ ( echo $SHLVL )
1
$ bash -c 'echo $SHLVL'
2

My function is as follows:

exit () {
    if [[ $SHLVL -eq 1 ]]; then
        printf '%sn' "Nice try!" >&2
    else
        command exit
    fi
}

This won’t allow me to use exit within subshells though:

$ exit
Nice try!
$ (exit)
Nice try!

What is a good method to detect whether or not I am in a subshell?

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

In bash, you can compare $BASHPID to $$

$ ( if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi )
subshell
$   if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi
not subshell

If you’re not in bash, $$ should remain the same in a subshell, so you’d need some other way of getting your actual process ID.

One way to get your actual pid is sh -c 'echo $PPID'. If you just put that in a plain ( … ) it may appear not to work, as your shell has optimized away the fork. Try extra no-op commands ( : ; sh -c 'echo $PPID'; : ) to make it think the subshell is too complicated to optimize away. Credit goes to John1024 on Stack Overflow for that approach.

Method 2

How about BASH_SUBSHELL?

BASH_SUBSHELL
      Incremented by one within each subshell or subshell environment when the shell
      begins executing in that environment. The initial value is 0.

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1

Method 3

[this should’ve been a comment, but my comments tend to be deleted by moderators, so this will stay as an answer that I could use it as a reference even if deleted]
Using BASH_SUBSHELL is completely unreliable as it be only set to 1 in some subshells, not in all subshells.

$ (echo $BASH_SUBSHELL)
1
$ echo $BASH_SUBSHELL | cat
0

Before claiming that the subprocess a pipeline command is run in is not a really real subshell, consider this man bash snippet:

Each command in a pipeline is executed as a separate process (i.e., in a subshell).

and the practical implications — it’s whether a script fragment is run a subprocess or not which is essential, not some terminology quibble.

The only solution, as already explained in the answers to this question is to check whether $BASHPID equals $$ or, portably but much less efficient:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you're in a subshell
fi


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

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x