The syntax $(...)
isn't "shell expansion", it is "command substitution".
With this syntax a sub-shell is created, the command inside is executed and the stdout returned on the comamnd line. So, for example,
x=$(cd /tmp ; ls)
will run the cd
command in the subshell, which means the current directory of the main process is left untouched.
In a similar way, cd $(popd)
will cause the popd
to run in the child process and so impacts only the child process; the parent process is untouched.
You can see that it has affected the child process with this simple test:
$ pushd /tmp
/tmp ~
$ pushd /
/ /tmp ~
$ dirs
/ /tmp ~
$ cd $(popd ; dirs >&2)
/tmp ~
$ dirs
/tmp /tmp ~
The dirs >&2
shows that inside the $(...)
shell the directory stack has been popped, but because this is a child process the parent stack is untouched.