Zsh – Detect When Running Within Subshell of Another Shell

zsh

This is a slightly esoteric Zsh question, of which there may not be a concrete answer.

I am trying to detect a particular context of a Zsh when instantiated in a particular way. How to do this is not immediately obvious given the environment variables commonly used for this purpose (such as $SHLVL, $ZSH_SUBSHELL, $ZSH_EVAL_CONTEXT, and $-), at least as far as I am aware.

Here is a Zsh instantiated in a normally detectable way:

export HELLO='world'
zsh -c 'echo "$SHLVL $ZSH_SUBSHELL $ZSH_EVAL_CONTEXT $- $HELLO"'
# 2 0 cmdarg 569X world

As opposed to a Zsh instantiated in a way that obscures detection:

export HELLO='world'
(zsh -c 'echo "$SHLVL $ZSH_SUBSHELL $ZSH_EVAL_CONTEXT $- $HELLO"')
# 1 0 cmdarg 569X world

The Zsh called within the subshell has access to the parent environment's variables, as is evident by the presence of world, however $SHLVL is tricked into reporting 1 and unfortunately $ZSH_SUBSHELL, with a value of 0, is of no help in this case.

How can I detect, using standard detection methods, the fact that the second case has access to a parent environment? I don't want to have to rely on checking variables set by my self in the parent context that dirty the environment solely for this purpose, if I don't have to.

Perhaps it would help me if someone could explain how Zsh deciphers the $SHLVL and why placing that code in a subshell obscures that detection. My rough understanding is that the subshell forks the environment (anything that is exported), and that $SHLVL is not exported.

Best Answer

That was a bug (regression) introduced in 1999 by that change and now fixed (by that change (commit))

Basically, in:

(cmd1; cmd2)

zsh forks a child process for the subshell, then another process for cmd1. But for cmd2, given that it's the last command in the subshell, zsh optimises out the fork. That's as if we had done:

(cmd1; exec cmd2)

Or in other words, zsh does an implicit/fake exec here.

Now the bug was that in that case, zsh forgot that we were in a subshell and assumed that implicit exec was actually terminating the shell (replacing it with cmd2), and was then decrementing $SHLVL as cmd2's parent would not be zsh anymore.

Here however, we're in a subshell, so that implicit exec is not terminating the shell.

Thanks for bringing that up. That allowed to fix it and find related bugs in bash and tcsh.