1. Introduction

Shells offer many features to avoid repetition, optimize tasks, and automate processes. Variables are critical to most of them. Even simple actions can benefit from stored values and conditional expression checks.

In this tutorial, we explore ways to use shell variables when changing to a new path. First, we discuss the current working directory and how to switch it. Next, we explain the simple case of using direct paths in regular shell variables. After that, we turn to the specifics of storing a path in an array variable. Finally, we enumerate several potentially critical pitfalls related to paths in variables.

We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.

2. Current Working Directory and Directory Changes

When using the shell, the current working directory is either the initial one set for the user, commonly $HOME, or the last one we switched to with a command like cd, pushd, popd, and similar. Its value can be critical for many reasons and operations:

  • performance
  • command context
  • default storage
  • temporary data
  • permissions
  • redirection

For example, Vi even has an internal :pw[d] command to get the current working directory it was called from:

$ pwd
/home/baeldung
$ vi
[...]
:pwd
[...]
/home/baeldung

Here, we first get the output of pwd in the shell. After that, we verify it’s the same when checked from within Vi.

To change the current working directory, we usually leverage the cd command in the shell:

$ pwd
/home/baeldung
$ cd /dir/subdir
$ pwd
/dir/subdir

In fact, we can do the same from Vi as well:

$ pwd
/home/baeldung
$ vi
[...]
:pwd
[...]
/home/baeldung
[...]
:cd /dir
[...]
:pwd
/dir
:quit
$ pwd
/home/baeldung

The :cd command in Vi acts similarly to cd in the shell but doesn’t modify the current working directory of the latter.

Changing paths with literal values is acceptable in interactive shells. However, automated scripts might not hardcode values, instead relying on variables to store and change paths.

3. Change to Directory Path in Variable

Since all variables in Bash are essentially character strings at their core, storing a path can be very straightforward:

$ DIRPATH='/dir/subdir'

After doing so, we can simply change to it:

$ pwd
/home/baeldung
$ echo "$DIRPATH"
/dir/subdir
$ cd "$DIRPATH"
$ pwd
/dir/subdir

Here, we output the variable value with echo after checking the path. Then, we switch to and verify the new path.

4. Change to Directory Path in Array

In fact, we can even store paths in arrays.

On the one hand, we can have each part of the path, including separators, in a separate array element:

$ SADIRPATH=(/ dir / subdir)
$ echo "${SADIRPATH[0]}"
/
$ echo "${SADIRPATH[1]}"
dir
$ echo "${SADIRPATH[2]}"
/
$ echo "${SADIRPATH[3]}"
subdir
$ echo "${SADIRPATH[*]}"
/ dir / subdir
$ echo "${SADIRPATH[@]}"
/ dir / subdir

On the other hand, since the / slash separator is standard, we can leave only the directory names as long as we know whether the path is relative or absolute:

$ ADIRPATH=(dir subdir)
$ echo "${ADIRPATH[0]}"
dir
$ echo "${ADIRPATH[1]}"
subdir
$ echo "${ADIRPATH[*]}"
dir subdir
$ echo "${ADIRPATH[@]}"
dir subdir

Notably, by default, printing the whole array inserts separators, i.e., the first $IFS character. Consequently, we may experience issues.

To mitigate them, we have several options to reduce or join the array to a proper path string. Let’s go over each for both array path types.

4.1. Modify $IFS

First, we can reset $IFS and output the array in a subshell:

$ DIRPATH=$(IFS=; echo "${SADIRPATH[*]}")
$ echo "$DIRPATH"
/dir/subdir

When dealing with an array without separators, we can assign / slash to $IFS, possibly prefixing the echo string as well:

$ DIRPATH=$(IFS=/; echo "/${ADIRPATH[*]}")
$ echo "$DIRPATH"
/dir/subdir

In both cases, the subshell protects our main environment from changes but still returns the augmented data in $DIRPATH. This works since the combination of double quotes and the * wildcard index for an array results in a single string of all elements with $IFS separating them.

4.2. Using printf

As with other string manipulation methods, we may employ printf with its %s format string.

First, let’s try it with an array with separators:

$ printf -v DIRPATH '%s' "${SADIRPATH[@]}"
$ echo "$DIRPATH"
/dir/subdir

Now, we can see the result when the array contains no separators:

$ printf -v DIRPATH '/%s' "${ADIRPATH[@]}"
$ echo "$DIRPATH"
/dir/subdir
$ printf -v DIRPATH '%s/' "${ADIRPATH[@]}"
$ echo "$DIRPATH"
dir/subdir/

Since we can optionally end paths to directories with a / slash, a / slash prefix or suffix in the format operand differentiates between a relative and absolute path.

Here, we leverage two features of printf:

  • the -v flag of printf is used to specify a variable ($DIRPATH) to store the output in
  • according to the POSIX printf specification, the format operand (‘/%s’) is reused for all arguments, i.e., all array elements

In addition, the difference between the @ asperand and * wildcard array indices is again notable. Here, @ asperand serves to supply printf with array elements one by one instead of a single string. This way, we can print them without any separators.

4.3. for Loop

Finally, we can simply iterate over all array elements, concatenating them to a single variable:

$ unset DIRPATH; for part in "${SADIRPATH[@]}"; do DIRPATH+="$part"; done
$ echo "$DIRPATH"
/dir/subdir

When the array contains no separators, we again prepend or append a / slash depending on whether the path is relative:

$ unset DIRPATH; for part in "${ADIRPATH[@]}"; do DIRPATH+="/$part"; done
$ echo "$DIRPATH"
/dir/subdir
$ unset DIRPATH; for part in "${ADIRPATH[@]}"; do DIRPATH+="$part/"; done
$ echo "$DIRPATH"
dir/subdir/

After any of these transformations, we can supply the path to commands like cd, pushd, and similar.

5. Pitfalls

There are some common reasons for an invalid path:

  • typos
  • syntax problems
  • unexpected whitespace, e.g., at the end of a variable
  • character escaping
  • capitalization

These apply to literal paths as well. On the other hand, we can experience issues when storing paths in variables instead of using them directly.

5.1. Quoting

Quotes change the context of many operations.

For example, single quotes prevent interpolation, while using double quotes preserves newlines, which are otherwise removed:

$ SPECPATH='/dir/sub
dir'
$ echo $SPECPATH
/dir/sub dir
$ echo "$SPECPATH"
/dir/sub
dir

Since paths can contain newlines, considering this is critical for preserving the correct value.

5.2. Special Characters

Some special characters might not behave well when stored in variables or without quotes. To check for special characters in a variable that should contain a path, we can use a here-string with cat and its –show-all flag:

$ cat --show-nonprinting <<< "$SPECPATH"
/dir^E/sub$
dir$

In this case, we see newlines as $ dollar signs and the <ENQ> character as ^E with the –show-nonprinting or -v notation.

5.3. Home Directory

In many contexts, a leading tilde has a special meaning. In fact, it results in a tilde expansion, and it usually gets replaced with the value of the $HOME variable:

$ echo ~
/home/baeldung
$ echo $HOME
/home/baeldung
$ echo ~/dir/subdir
/home/baeldung/dir/subdir

If we improperly store paths like ~/dir/subdir in variables, using them might not invoke a tilde expansion. Consequently, they become the same as ./~/dir/subdir, i.e., a relative path beginning with a directory with the literal name ~ tilde:

$ TILDEPATH=~/dir/subdir
$ echo $TILDEPATH
/home/baeldung/dir/subdir
$ TILDEPATH="~/dir/subdir"
$ echo $TILDEPATH
~/dir/subdir
$ TILDEPATH=~/"dir/subdir"
$ echo $TILDEPATH
/home/baeldung/dir/subdir

Notably, it again partially comes down to quotes. If we want to preserve the meaning of the tilde while using quotes around the rest of the path string, we use ~/ as the suffix and start the quotes after.

5.4. Array Indexing

Getting indices wrong when using whole arrays can result in unexpected behavior:

$ echo $(IFS=; echo "${SADIRPATH[*]}")
/dir/subdir
$ echo $(IFS=; echo "${SADIRPATH[@]}")
/ dir / subdir
$ printf '%s' "${SADIRPATH[@]}"
/dir/subdir
$ printf '%s' "${SADIRPATH[*]}"
/ dir / subdir
$ unset DIRPATH; for part in "${SADIRPATH[@]}"; do DIRPATH+="$part"; done; echo "$DIRPATH"
/dir/subdir
$ unset DIRPATH; for part in "${SADIRPATH[*]}"; do DIRPATH+="$part"; done; echo "$DIRPATH"
/ dir / subdir

Using our earlier examples, we see the problems this seemingly minor replacement can cause by introducing whitespace.

6. Summary

In this article, we explored ways to store and change to a directory path via a variable.

In conclusion, while there are important pitfalls, we can employ regular and array variables to hold paths and use them to change the current directory.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.