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:
zsh
forks a child process for the subshell, then another process forcmd1
. But forcmd2
, given that it's the last command in the subshell,zsh
optimises out the fork. That's as if we had done:Or in other words,
zsh
does an implicit/fakeexec
here.Now the bug was that in that case,
zsh
forgot that we were in a subshell and assumed that implicitexec
was actually terminating the shell (replacing it withcmd2
), and was then decrementing$SHLVL
ascmd2
's parent would not bezsh
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
andtcsh
.