Tilde in PATH: Why It May Not Expand to HOME10 min read2,068 words

Tilde in PATH: Why It May Not Expand to HOME

A quoted tilde in PATH stays literal, not your home directory. Learn how Bash and Zsh handle it, how to check your PATH, and how to fix it safely.

tilde in PATHPATH environment variablebash tilde expansionzsh configurationshell security
Tilde in PATH: Why It May Not Expand to HOME

Why ~ in Your PATH Can Point Somewhere Else Entirely

A small detail in a shell config can change where your system looks for commands. If you add ~/.local/bin to PATH inside quotes, the tilde might not expand to your home directory. In that case, it stays a literal ~, and the shell can end up searching for a directory literally named ~ relative to your current working directory.

I ran into this while using the nono agent sandboxing tool. It warned that a PATH entry it could write to was ~/.local/bin/. At first, that sounded like a typo. After checking how the value was built, it made sense: a quoted tilde never turned into $HOME.

This is easy to miss because the config line can look "normal" and the command might still work. But it matters more than tidy environment settings: PATH controls where the shell searches for executables, so a relative entry can behave differently depending on where you run things from. Here's how the expansion works, how to spot the problem, and how to fix it without relying on fragile quoting quirks.

What a tilde in PATH actually means

PATH is a colon-separated list of directories. When you type a command like git, your shell checks those directories in order and runs the first executable it finds. If it doesn't find one, you get a "command not found" error.

A typical entry for a user's local executables might look like:

/home/alex/.local/bin

On macOS, it might be:

/Users/alex/.local/bin

The tilde ~ is shorthand that many shells expand to your home directory - but only in certain contexts. Whether it expands depends on where it appears and whether it's quoted.

A quoted tilde can stay literal

Consider this line:

export PATH="$PATH:~/.local/bin/"

On the surface, it looks like you're appending your local bin directory. But because the tilde is inside double quotes, Bash won't expand it there. Instead, your PATH literally gets the characters ~/.local/bin/.

Now suppose your current directory is /work/project. That PATH entry can be interpreted as a relative path equivalent to:

/work/project/~/.local/bin/

And the key point is this: the shell expands values while processing your config line, not later when it searches PATH. When it's time to do the command lookup, it uses the string exactly as it was stored - no home-directory magic happens per entry.

A successful command can hide the mistake

A broken PATH entry doesn't always produce an obvious error. If there happens to be a directory named ~ (literally) under your current working directory, the shell may find and run an executable from there.

For example:

mkdir -p ./~/.local/bin

printf '#!/bin/sh\necho "ran from literal tilde directory"\n' > ./~/.local/bin/demo

chmod +x ./~/.local/bin/demo

PATH="~/.local/bin" demo

From that directory, the shell can run demo. But that doesn't mean the tilde expanded - it means the relative PATH entry matched ./~/.local/bin in the current folder.

That's why this problem can survive a quick test: things run, but from the wrong place. Tools that inspect PATH entries may reveal it more clearly than a routine "it worked" check.

Why the quoted form does not expand

Quoting isn't just formatting - it controls which expansions and parsing rules apply. Double quotes allow some expansions (like $HOME), but they block tilde expansion in ordinary quoted text.

For example:

printf '%s\n' "~/.local/bin"

prints the tilde as written.

Whereas:

printf '%s\n' "$HOME/.local/bin"

prints your actual home directory followed by /.local/bin.

Bash also has special tilde-expansion rules for some unquoted tilde positions - especially in certain assignment contexts. Bash's documentation covers expansion for an unquoted tilde-prefix at the beginning of a word, and in assignment values immediately after = or :. That's why this might "work" in Bash and Zsh:

export PATH=$PATH:~/.local/bin

Here, the tilde follows an unquoted colon inside an assignment, so the shell can expand it. Still, the line is easier to accidentally break. If the value gets edited, split across commands, or affected by whitespace, you may end up not using the same parsing context. Unquoted variables can also trigger other shell behavior you don't want in config code.

Use $HOME to make the intent explicit

A safer pattern is:

export PATH="$PATH:$HOME/.local/bin"

$HOME expands inside double quotes, so you get an absolute path. And because it's in quotes, spaces or other special characters in $HOME won't get mangled by word splitting or pathname expansion.

This isn't saying the unquoted form is always wrong. It can be valid in the right shell and context. The practical difference is that $HOME states exactly what you mean and behaves predictably. In shell configuration, explicit usually costs you almost nothing - and saves you from debugging a path that only "looks right."

For more detail, see the Bash manual's tilde expansion section and the Zsh expansion documentation.

How to check and fix your PATH

First, don't just reread the line you meant to write - print the value your shell actually has:

echo "$PATH"

To make it easier to inspect, print one entry per line:

printf '%s\n' "$PATH" | tr ':' '\n'

Then look for entries that still start with a tilde. A quick check:

echo "$PATH" | grep -- '~'

Or, if you want to narrow it down further:

echo "$PATH" | tr ':' '\n' | grep -- '~'

If you see something like ~/.local/bin/, that value hasn't become an absolute home-directory path. Fix the config by changing the relevant line to:

export PATH="$PATH:$HOME/.local/bin"

Check the file your shell actually reads

Startup files depend on both the shell and how it's started. Common ones include:

  • ~/.bashrc for interactive Bash shells.
  • ~/.bash_profile or ~/.profile for login-shell setup, depending on your system.
  • ~/.zshrc for interactive Zsh shells.
  • ~/.zprofile for Zsh login-shell setup.

Don't assume the first file you find is the one that matters. Search likely config files for the PATH assignment:

grep -n 'PATH=' ~/.bashrc ~/.bash_profile ~/.profile ~/.zshrc ~/.zprofile 2>/dev/null

Update the line that appends the local bin directory, then start a new shell or reload what you changed. For Bash, for example:

source ~/.bashrc

After that, confirm by printing PATH again. Reading the source line alone isn't enough - the current shell may have inherited PATH from somewhere else (another startup file or a parent process).

Avoid duplicate entries when updating PATH

Another common issue is adding the same directory every time a startup file runs. It makes PATH longer and harder to reason about. If that's a real concern in your setup, guard before appending:

case ":$PATH:" in

*":$HOME/.local/bin:"*) ;;

*) export PATH="$PATH:$HOME/.local/bin" ;;

esac

This helps when startup files get sourced repeatedly or when multiple files share environment setup. If your personal config loads once, a straightforward append is usually fine. Use the guard when duplication actually becomes a problem, not because it looks clever.

Practical risks and edge cases

A literal tilde in PATH isn't just cosmetic. PATH entries that are relative - whether they start with ~ or . - depend on the current working directory. That means command lookup can resolve to different executables depending on where you run from.

That can cause confusing "works here, fails there" behavior. It can also turn into a security issue. If an earlier directory in PATH is writable by an untrusted process - or contains something unexpected - the shell may run that executable instead of the one you intended. That's why sandboxing tools care about which PATH directories are writable. Their warning is about what might be found and executed, not just that a string looks weird.

Prefer absolute PATH entries

For personal tool directories, an absolute path like $HOME/.local/bin is easier to reason about than a relative one. It points to the same location no matter what your current directory is. That consistency matters in scripts, interactive terminals, and sandboxed environments.

None of this means every writable directory in PATH is automatically unsafe. Your own local bin directory is often writable by you - and it's exactly where user-installed tools typically live. The real questions are:

  • Who can write to that directory?
  • Which executables could end up there?
  • Does anything run PATH entries automatically, and with elevated privileges?
  • Is the directory absolute, or does it change meaning based on the working directory?

The right policy depends on the environment. A local development shell and a privileged service shouldn't necessarily use the same PATH assumptions.

Don't treat grep as a complete PATH validator

Searching for ~ can catch this specific mistake fast, but it's not a full audit. PATH can include empty entries, relative paths, nonexistent directories, duplicates, or directories whose permissions you should review.

Some shells also treat empty PATH components specially, so don't assume "no tilde found" means everything is safe. If you're debugging a critical script or sandbox, inspect the entries and permissions directly, and verify which executable the shell actually resolves.

For a command like python, you can ask the shell what it would use:

command -v python

In Bash, you can also check more detail with:

type -a python

These help, though aliases, functions, and shell-specific command resolution can affect what you see. When you need to confirm a specific binary, check the resolved path and its ownership/permissions rather than trusting only the command name.

Conclusion

A tilde in PATH isn't automatically shorthand for your home directory. If it's quoted, it can stay literal. Then, when PATH is searched, that entry can behave like a relative path under your current directory. A command might still run, which is exactly why this mistake can slip by.

Use $HOME in quoted PATH assignments, inspect the PATH value your shell actually has, and treat relative or writable PATH entries as deliberate choices - not accidental side effects. If you manage shell configuration or sandbox rules, it's worth doing one quick scan: it can catch a subtle source of inconsistent command lookup.

Frequently Asked Questions

No. Tilde expansion depends on where the tilde appears and whether it is quoted. Inside a quoted string such as "~/.local/bin", it remains literal. Use $HOME when constructing PATH entries.
Bash and Zsh can expand a tilde in certain unquoted assignment contexts, including after a colon in an assignment value. The quoted form behaves differently. Using `export PATH="$PATH:$HOME/.local/bin"` makes the intended expansion explicit.
It can be. A relative entry is interpreted in relation to the current working directory, so command lookup may change as you move between directories. If an unexpected or untrusted executable is found there first, it may run instead of the command you intended.
Edit the startup file that adds it and replace the tilde with `$HOME`, for example: `export PATH="$PATH:$HOME/.local/bin"`. Reload the relevant configuration or start a new shell, then inspect PATH one entry per line to confirm the literal tilde is gone.

Related articles