Resetting the Working Directory on Shell Function Exit

Christian Emmer
Christian Emmer
Jul 24, 2025 · 3 min read
Resetting the Working Directory on Shell Function Exit

Sometimes you need to change the working directory in a shell function. You should take care to reset it back after.

Here's an example scenario: you're writing a complicated shell script that makes use of functions, or you're adding functions to your dotfiles to be invoked from your shell. Within that function, you want to execute a command that needs to be within a specific working directory. However, you don't want this to affect the rest of your script or the current shell you have open.

There's an easy, portable solution for this!

See "Resetting the Working Directory on Shell Script Exit" for when you only need to reset the working directory at the very end of a script.

Resetting the Working Directory on Shell Script Exit
Resetting the Working Directory on Shell Script Exit
Mar 5, 2024 · 3 min read

Sometimes you need to change the working directory in a shell script. You should take care to reset it back after.

The portable solution

You should use a subshell with POSIX-compliant shells. Subshells are separate, child processes of the shell that invoked the function, and manipulating the environment (including the working directory) in them does not affect the parent process. Subshells are created using parentheticals:

#!/usr/bin/env bash
set -euo pipefail

function cd_and_exec() {
    (
        echo "I won't affect my parent"
        cd ~
    )
}

echo "I don't want my working directory to change."
cd_and_exec
echo "My working directory hasn't changed!"

You might be familiar with the $() syntax of command substitution , these are using subshells!

Subshells isolate the entire environment from the parent process, which may or may not be desirable for your use case, including:

  • Variables:

    $ (foo="bar") && echo "${foo:-unset}"
    unset

    including exported variables!

    ( export PATH=/my/bin/dir:$PATH; my_bin ) && echo $PATH
  • trapped signals:

    $ ( trap 'echo "subshell exited"' EXIT )
    subshell exited
  • umask file mode creation mask:

    ( umask 077; touch private_file )
  • File descriptors:

    ( exec > output.log; echo "This will be logged" )

Other common use cases of subshells include:

  • Output can be redirected together:

    ( echo "lorem"; echo "ipsum" ) > output.log

    or captured together:

    output=$( echo "hello"; echo "world" )
  • Exit codes are returned:

    $ (true && false) || echo "the subshell failed!"
    the subshell failed!

However, subshells can be undesirable because there's a performance cost to forking a process.

Shell-specific solutions

Most shells have some way to trap exit signals, but some also have a way to trap function return signals. These solutions aren't recommended, even if you think you will always be using the same shell.

  • Bash can trap RETURN signals:

    #!/usr/bin/env bash
    
    function foo() {
        trap "cd \"${PWD}\"" RETURN
        echo "I'm safe to change the working directory"
        cd ~
    }
  • Zsh's TRAPEXIT will fire when the surrounding function exits:

    #!/usr/bin/env zsh
    
    function foo() {
        trap "cd \"${PWD}\"" EXIT
        echo "I'm safe to change the working directory"
        cd ~
    }