I have a scenario that calls for command substitution without using a subshell. I have a construct like this:

pushd $(mktemp -d)

Now I want to exit and remove the temporary directory in one go:

rmdir $(popd)

However that doesn't work because popd doesn't return the popped directory (it returns the new, now current, directory) and also because it's performed in a subshell.

Something like

dirs -l -1 ; popd &> /dev/null

will return the popped directory but it can't be used like this:

rmdir $(dirs -l -1 ; popd &> /dev/null)

because the popd will only affect the subshell. What is called for is the ability to do this:

rmdir { dirs -l -1 ; popd &> /dev/null; }

but that's invalid syntax. Is it possible to achieve this effect ?

(note: I know I can save the temporary directory in a variable; I was trying to avoid the need to do so and learn something new in the process!)

  • 1
    I'd save the directory to a variable; that way, a trap handler can cleanup the directory should the process be poked by a signal.
    – thrig
    Aug 3, 2016 at 14:18
  • The answer is no.
    – h0tw1r3
    Aug 3, 2016 at 14:19
  • It looks like on fish, the equivalent of command substitution, (), changes the outer shell's folder. It's usually annoying, but in cases like this it's useful, I'm sure.
    – trysis
    Aug 3, 2016 at 16:52

5 Answers 5


The choice of the title of your question is a bit confusing.

pushd/popd, a csh feature copied by bash and zsh, are a way to manage a stack of remembered directories.

pushd /some/dir

pushes the current working directory onto a stack, and then changes the current working directory (and then prints /some/dir followed by the content of that stack (space-separated).


prints the content of the stack (again, space separated) and then changes to the top element of the stack and pops it from the stack.

(also beware that some directories will be represented there with their ~/x or ~user/x notation).

So if the stack currently has /a and /b, the current directory is /here and you're running:

 pushd /tmp/whatever

pushd will print /tmp/whatever /here /a /b and popd will output /here /a /b, not /tmp/whatever. That's independent of using command substitution or not. popd cannot be used to get the path of the previous directory, and in general its output cannot be post processed (see the $dirstack or $DIRSTACK array of some shells though for accessing the elements of that directory stack)

Maybe you want:

pushd "$(mktemp -d)" &&
popd &&
rmdir "$OLDPWD"


cd "$(mktemp -d)" &&
cd - &&
rmdir "$OLDPWD"

Though, I'd use:

tmpdir=$(mktemp -d) || exit
  cd "$tmpdir" || exit # in a subshell 
  # do what you have to do in that tmpdir
rmdir "$tmpdir"

In any case, pushd "$(mktemp -d)" doesn't run pushd in a subshell. If it did, it couldn't change the working directory. That's mktemp that runs in a subshell. Since it is a separate command, it has to run in a separate process. It writes its output on a pipe, and the shell process reads it at the other end of the pipe.

ksh93 can avoid the separate process when the command is builtin, but even there, it's still a subshell (a different working environment) which this time is emulated rather than relying on the separate environment normally provided by forking. For example, in ksh93, a=0; echo "$(a=1; echo test)"; echo "$a", no fork is involved, but still echo "$a" outputs 0.

Here, if you want to store the output of mktemp in a variable, at the same time as you pass it to pushd, with zsh, you could do:

pushd ${tmpdir::="$(mktemp -d)"}

With other Bourne-like shells:

unset -v tmpdir
pushd "${tmpdir=$(mktemp -d)}"

Or to use the output of $(mktemp -d) several times without explicitly storing it in a variable, you could use zsh anonymous functions:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"
  • I understand pushd and popd to work as you describe and that they're independent of command substitution - no confusion there! However, you answered by own problem by revealing $OLDPWD. I can do popd; rmdir $OLDPWD. That;s the answer to my problem - everything else just confirms what I thought. Command substitution appears to be a way to solve it but it isn't because of the subshell and you can't do command substitution without a subshell, So thanks for revealing OLDPWD - that's exactly what I need!
    – starfry
    Aug 3, 2016 at 14:47
  • @starfry, rmdir $(popd) causes popd to run in a sub-shell which means it won't change the current directory, but even if it did not run in a subshell, the output of popd will not be the temporary directory, it will be a space separated list of directories not including that temp dir. Which is where I'm saying you're confused. Aug 3, 2016 at 14:59
  • but I thought I had said that in my question: "popd doesn't return the popped directory (it returns the new, now current, directory) and also because it's performed in a subshell." Admittedly it returns a list, but the first in the list is the one I was referring to.
    – starfry
    Aug 3, 2016 at 15:57
  • @starfry, OK sorry. Let's say I was the one confused by the title of the question and didn't read the rest carefully enough. Aug 3, 2016 at 17:30
  • well your answer was still good and pointed me in the right direction. I never knew about that shell variable OLDPWD. I must remember to one day read man bash from end-to-end !
    – starfry
    Aug 3, 2016 at 19:50

You could unlink the directory first, before leaving it:

rmdir "$(pwd -P)" && popd


rmdir "$(pwd -P)" && cd ..   # yes, .. is still usable

but note that pushd and popd are really tools for interactive shells, not for scripts (that's why they are so chatty; real scripting commands are silent when they succeed).


I had a similar problem so I'll post my solution here. Apparently there's not a way to avoid to span a subshell when using $(command) so the only solution that I got is to use a global variable -> pass it to the function and use eval. My case was a bit trickier. I wanted to open a file descriptor in a function and pass back the fd number to the main function. The problem is that the file descriptor is opened in the subshell and not in the main:

open_fd_and_return() {
    local _my_file="${1}" _my_fd_no

    exec {_my_fd_no}>"${_my_file}" || die "impossible to open the fd"
    echo "${_my_fd_no}"
my_file="$(mktemp --dry-run)"
my_fd="$(open_fd_and_return "${my_file}")"
echo "test $$" >&"${my_fd}"
rm "${my_file}"
exec {my_fd}>&-

this return "bad file descriptor error"

my solution for now is to use eval (even if uglier)

open_fd_and_return_eval() {
    local _my_var="${1}"
    local _my_file="${2}"
    local _my_fd_no

    exec {_my_fd_no}>"${_my_file}" || die "impossible to open the fd"
    eval "${_my_var}=\${_my_fd_no}"

my_file="$(mktemp --dry-run)"
open_fd_and_return_eval "my_fd" "${1}"
echo "test $$" >&"${my_fd}"
rm -- "${my_file}"
exec {my_fd}>&-

This works. Hopefully this is useful for someone.


In bash, dirs provide a list of remembered directories by the pushd/popd method.

Also, dirs -1 prints the last directory included in the list.

So, to remove the directory created previously by executing pushd $(mktmp -d), use:

rmdir $(dirs -1)

And then, popd the already removed directory from the list:

popd > /dev/null

All in one line:

rmdir $(dirs -1); popd > /dev/null

And adding the option (-l) to avoid the ~/x or ~user/x notation:

rmdir $(dirs -l -1); popd > /dev/null

Which is remarkably similar to the line you asked for.
Except that I would not use &> as it would hide any error reporting by popd.

Note: The directory will remain after rmdir as it is the pwd at that point. And will be actually unlinked after the popd command (no remaining links used).

There is an option to use, for shells that support the variable "OLDPWD" (most bourne-like shells: ksh, bash, zsh have $OLDPWD). It is interesting to note that ksh does not implement dirs, pod, pushd by default (lksh, dash and others also don't have popd available, so, are not usable here):

popd && rmdir "$OLDPWD"

Or, more idiomatic (same list of shells as above):

popd && rmdir ~-
  • The problem with this, and what I was trying to solve, is that you cannot rmdir the current directory which is where you'd be when doing this. You need to effect the popd before doing the rmdir but you need to know what to remove and popd does not tell you that. I, like you, thought dirs -l -1 was the answer but have since discovered that the answer is actually to use $OLDPWD.
    – starfry
    Aug 4, 2016 at 9:36
  • @starfry I believe that the shortest answer is to use: popd && rmdir ~-.
    – user79743
    Aug 4, 2016 at 10:48
  • @starfry You could actually remove the current directory, no problem, provided it is empty (without forcing the erase). You could even call rmdir "$PWD" all right. Also, the current directory should be the one created by mktmp -d (if pushd $(mktemp -d) was executed beforehand) and to which pushd moves the pwd. So, yes, after pushd and before popd, the directory created is stored in $(dirs -1), I fail to see any problem.
    – user79743
    Aug 4, 2016 at 10:51

To answer the generic

Is it possible to perform shell command substitution without using a subshell?

question, command substitution in the fish does not run in a subshell:

$ set dir (cd /etc; and pwd)
$ echo -- $dir $PWD
/etc /etc

Does change the current working directory and sets dir.

ksh93 and recent versions of mksh also support a ${ cmd; } form of command substitution that doesn't run in a subshell.

$ dir=${ cd /etc && pwd; }
$ print -r -- "$dir" "$PWD"
/etc /etc

mksh uses a tempfile for that.

With other shells, you can always use a tempfile:

cmd > "$tempfile"
  • I can't wrap my head around the idea of creating a file on the FS to store the contents of a transient variable.
    – Rolf
    Sep 1, 2023 at 18:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .