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
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
export
ed variables!( export PATH=/my/bin/dir:$PATH; my_bin ) && echo $PATH
trap
ped 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 ~ }