Table of Contents#
- Understanding the Problem: Why ~/Path Fails in Scripts
- The Root Cause: Tilde Expansion in Shells
- Diagnosing the Issue: Examples and Error Messages
- How to Fix It: Solutions for Tilde Paths in Scripts
- Best Practices for Path Handling in Shell Scripts
- Conclusion
- References
Understanding the Problem: Why ~/Path Fails in Scripts#
Let’s start with a concrete example. Suppose you write a simple script (create_dir.sh) to create a directory in your home folder:
#!/bin/bash
# create_dir.sh
DIR="~/my_new_folder" # Path with tilde (~)
mkdir "$DIR" # Attempt to create the directoryWhen you run this script with bash create_dir.sh, you get an error:
mkdir: cannot create directory ‘~/my_new_folder’: No such file or directory
But if you run mkdir ~/my_new_folder directly in your interactive shell (e.g., terminal), it works! So why does the same path fail in a script?
The Root Cause: Tilde Expansion in Shells#
The key to solving this lies in understanding tilde expansion and the order of operations in shell expansion.
What is Tilde Expansion?#
In Unix shells (like Bash, Zsh, or Dash), the tilde (~) is a special character that expands to the current user’s home directory (e.g., /home/your_username). For example:
~expands to/home/your_username~/Documentsexpands to/home/your_username/Documents~otheruserexpands to/home/otheruser(ifotheruserexists)
The Order of Shell Expansions#
Shells process commands in a specific order of expansions. From the Bash manual, the order is:
- Brace Expansion (
{a,b}→a b) - Tilde Expansion (
~→/home/user) - Parameter and Variable Expansion (
$VAR→ value ofVAR) - Arithmetic Expansion (
$((1+1))→2) - Command Substitution (
$(command)→ output ofcommand) - Word Splitting
- Filename Expansion (globbing, e.g.,
*.txt)
Why ~/Path Fails in Variables#
Notice that tilde expansion happens before variable expansion. When you store ~/my_new_folder in a variable (e.g., DIR="~/my_new_folder"), the shell treats the ~ as a literal character during variable assignment because:
- Tilde expansion occurs early (step 2), but the variable is defined as a string (
"~/my_new_folder"), so no expansion happens here. - Later, when the variable is expanded (step 3, e.g.,
mkdir "$DIR"), tilde expansion has already finished. The shell sees~/my_new_folderas a literal path with a~character, not a home directory shortcut.
Diagnosing the Issue: Examples and Error Messages#
To confirm the problem, let’s compare behavior in an interactive shell vs. a script.
Interactive Shell: Quoting vs. Not Quoting#
In an interactive shell, if you avoid quotes when assigning the variable, tilde expansion does occur during assignment (because tilde expansion happens before variable assignment in this case):
# Interactive shell example
DIR=~/my_new_folder # No quotes: tilde expands DURING assignment
echo "$DIR" # Output: /home/your_username/my_new_folder (expanded!)
mkdir "$DIR" # Works!But if you use quotes when assigning the variable (as in the script), tilde expansion does NOT occur:
# Interactive shell example with quotes
DIR="~/my_new_folder" # Quotes: tilde is treated as a literal
echo "$DIR" # Output: ~/my_new_folder (NOT expanded)
mkdir "$DIR" # Fails: "No such file or directory"Script Behavior#
In scripts, even without quotes during assignment, the behavior is consistent with the expansion order. However, scripts often use quotes around variables (a best practice to avoid word splitting), which preserves the literal ~ if the variable wasn’t expanded during assignment.
The Error Message Explained#
The error mkdir: cannot create directory ‘~/my_new_folder’ occurs because the shell tries to create a directory named ~ (a literal tilde) in the current working directory, then a subdirectory my_new_folder inside it. Since the ~ directory doesn’t exist, the command fails.
How to Fix It: Solutions for Tilde Paths in Scripts#
Now that we understand the root cause, let’s explore reliable solutions to use ~/path-like paths in scripts.
Solution 1: Use $HOME Instead of ~#
The simplest fix is to replace ~ with the $HOME environment variable, which explicitly expands to your home directory.
#!/bin/bash
# create_dir_fixed.sh
DIR="$HOME/my_new_folder" # Use $HOME instead of ~
mkdir "$DIR" # Now works!Why it works: $HOME is a variable that expands to your home directory (e.g., /home/your_username). Since variable expansion (step 3) replaces $HOME with its value, the path becomes /home/your_username/my_new_folder, which mkdir understands.
Solution 2: Expand Tilde During Variable Assignment#
If you prefer using ~, expand it during variable assignment by avoiding quotes around the path. This ensures tilde expansion (step 2) runs before the variable is stored.
#!/bin/bash
# create_dir_fixed.sh
DIR=~/my_new_folder # No quotes: tilde expands during assignment
mkdir "$DIR" # $DIR is already expanded (e.g., /home/user/my_new_folder)Why it works: Without quotes, the shell processes tilde expansion (step 2) before assigning the value to DIR. Thus, DIR stores the fully expanded path (e.g., /home/your_username/my_new_folder), not the literal ~.
Solution 3: Use eval to Force Tilde Expansion (Caution!)#
If you must keep the tilde in a quoted variable (e.g., dynamic paths), use eval to re-run tilde expansion. Note: eval executes strings as shell commands, so use this only with trusted input (avoid untrusted user input!).
#!/bin/bash
# create_dir_fixed.sh
DIR="~/my_new_folder" # Tilde in quoted variable (literal ~)
eval "mkdir $DIR" # eval forces tilde expansionWhy it works: eval re-parses the command string, triggering tilde expansion on ~/my_new_folder before executing mkdir.
Risks: If DIR contains spaces, special characters, or malicious input (e.g., ~/malicious; rm -rf /), eval will execute those commands. Avoid this unless you fully control the DIR value.
Solution 4: Expand ~otheruser for Other Users#
If the path belongs to another user (e.g., ~john/documents), use Solution 2 (unquoted assignment) to expand the tilde during variable creation:
#!/bin/bash
# create_johns_dir.sh
USER_DIR=~john/documents # Unquoted: expands to /home/john/documents
mkdir "$USER_DIR" # Works if user "john" existsBest Practices for Path Handling in Scripts#
To avoid tilde-related issues (and other path pitfalls) in scripts, follow these best practices:
- Prefer
$HOMEover~in variables: It’s explicit, readable, and avoids expansion-order confusion. - Quote variables when using them (e.g.,
mkdir "$DIR"): This prevents word splitting if paths contain spaces (e.g.,$HOME/My Documents). - Expand tildes during assignment if using
~: Use unquoted assignment (DIR=~/path) to ensure tilde expansion happens early. - Avoid
evalwith untrusted input:evalis powerful but risky. Use it only if you fully control the path. - Test paths with
echo: Before runningmkdirorcd, echo the path to verify expansion:DIR="$HOME/my_new_folder" echo "Creating directory: $DIR" # Verify the path is correct! mkdir "$DIR"
Conclusion#
The "mkdir fails with ~/path in scripts" issue is caused by the shell’s expansion order: tilde expansion happens before variable expansion, so ~ in a variable is treated as a literal. To fix it:
- Use
$HOME(e.g.,DIR="$HOME/path"), - Expand
~during variable assignment (e.g.,DIR=~/path), or - Use
eval(with caution) for dynamic paths.
By following these solutions and best practices, you’ll reliably handle home directory paths in shell scripts.