A practical guide to editing your PATH

Adding a directory to your PATH is one of those terminal tasks that feels obvious once you've done it a hundred times, but there's surprisingly little guidance that covers the whole process end to end. Most instructions just say “add this to ~/.bashrc,” but that assumes a lot: what if you're not using bash? What if your bash config lives in a different file? And how do you even determine which directory needs to be added?

Here's a step-by-step walkthrough of the full process, including how to identify your shell, locate your config file, and debug common issues.

Step 1: Identify your shell

Before you can edit your config, you need to know which shell you're running. The quickest way to find out:

ps -p $$ -o pid,comm=
  • bash prints something like 97295 bash
  • zsh prints something like 97295 zsh (and is the default on macOS as of 2024)
  • fish prints an error mentioning $fish_pid, since $$ isn't valid fish syntax — which itself is a giveaway

On Linux, bash is typically the default. This guide covers bash, zsh, and fish only.

Step 2: Locate your shell's config file

  • zsh: typically ~/.zshrc
  • bash: could be ~/.bashrc, but it's not always straightforward (see below)
  • fish: typically ~/.config/fish/config.fish — run echo $__fish_config_dir to be certain

Bash presents a complication: it has three possible config files — ~/.bashrc, ~/.bash_profile, and ~/.profile. Rather than memorizing the elaborate rules for which one gets loaded, test it directly:

  1. Add echo hi there to your ~/.bashrc
  2. Restart your terminal
  3. If you see “hi there,” ~/.bashrc is being used
  4. If not, remove that line and try ~/.bash_profile
  5. If that doesn't work either, try ~/.profile

Trial and error is the fastest way to be sure.

Step 3: Determine which directory to add

Suppose you've installed a program called http-server and it doesn't run:

$ npm install -g http-server
$ http-server
bash: http-server: command not found

Finding where a program actually lives isn't always obvious. Often the answer depends on how the installer is configured. A few avenues to explore:

  • Most installers (like cargo, npm, or homebrew) print PATH setup instructions during first-time configuration — pay attention to the output.
  • Some installers automatically modify your shell config to update PATH on your behalf.
  • Searching for “where does npm install things?” (or equivalent) often yields the answer.
  • Many tools offer a subcommand that reveals their install location:
    • Node/npm: npm config get prefix (then append /bin/)
    • Go: go env GOPATH (then append /bin/)
    • asdf: asdf info | grep ASDF_DIR (then append /bin/ and /shims/)

Step 3.1: Verify you've found the right directory

Before editing your config, confirm the directory is correct. For example, on a machine where http-server lives in ~/.npm-global/bin, you can test by invoking the program directly from that path:

$ ~/.npm-global/bin/http-server
Starting up http-server, serving ./public

If it runs, you've identified the correct directory to add.

Step 4: Edit your shell config

At this point, you should have two pieces of information: the directory to add (e.g., ~/.npm-global/bin/) and the location of your shell config (~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish). The syntax you add depends on your shell.

bash

Open your config file and add a line like:

export PATH=$PATH:~/.npm-global/bin/

Replace ~/.npm-global/bin with your actual directory.

zsh

The same syntax works as in bash, but zsh also supports a slightly more expressive variant:

path=(
  $path
  ~/.npm-global/bin
)

fish

Fish uses different syntax entirely:

set PATH $PATH ~/.npm-global/bin

Fish also offers the fish_add_path helper, discussed below.

Step 5: Restart your shell

Editing the config file has no effect until your shell rereads it. Either open a new terminal window (and close the old one, to avoid confusion), or run bash, zsh, or fish to spawn a fresh shell. Both approaches work reliably.

Once restarted, try running the program that previously failed.

Troubleshooting

Problem: the wrong version runs

If an unexpected version of a program is being executed, you may need to prepend the directory to PATH instead of appending it. For example, on a system with two python3 installations, which -a reveals both:

$ which -a python3
/usr/bin/python3
/opt/homebrew/bin/python3

The shell uses the first listing. To prefer the Homebrew version, place /opt/homebrew/bin at the beginning of PATH, inverting the usual order:

export PATH=/opt/homebrew/bin/:$PATH

In fish:

set PATH ~/.cargo/bin $PATH

Problem: the program is launched outside your shell

All of this assumes you're executing the program from your shell. If you're running it from an IDE, GUI, or cron job, these changes won't apply.

For cron jobs, two options are available:

  • Use the full path to the executable, e.g., /home/bork/bin/my-program
  • Set a complete PATH at the top of your crontab. Retrieve your shell's current PATH with echo "PATH=$PATH" and paste that value.

Problem: duplicate PATH entries complicate debugging

Starting a new shell with bash, zsh, or fish repeatedly can accumulate duplicate PATH entries, because each shell startup appends or prepends values. While duplicates rarely break anything, they make debugging harder. To manage this:

  • Debug PATH from a fresh terminal window, which avoids accumulation.
  • Deduplicate within your config — in zsh, typeset -U path does this.
  • Before adding a directory, check whether it's already present — in fish, fish_add_path --path /some/directory provides this guard.

Deduplication methods are shell-specific, and not every shell has a built-in mechanism.

Problem: shell history disappears after reloading

A common interaction in bash or zsh: a command fails, you update PATH, run bash to reload, press the up arrow — and your failed command isn't in history. This happens because bash, by default, doesn't persist history until the shell exits cleanly.

Two workarounds:

  • Instead of running bash to reload, use source ~/.bashrc (or source ~/.zshrc) within the current session.
  • Configure your shell to save history continuously, rather than only on exit. This setting is shell-specific — zsh's history options in particular can be intricate.

A note on source–based setup scripts

Some installers, like Rust's cargo, instruct you to source a script rather than adding a directory manually:

This is usually done by running one of the following (note the leading DOT):

. "$HOME/.cargo/env"        	# For sh/bash/zsh/ash/dash/pdksh
source "$HOME/.cargo/env.fish"  # For fish

The script configures your PATH and possibly other environment variables. Homebrew's brew shellenv is a similar pattern. You have two choices: follow the installer's suggestion, or figure out what directories it actually adds and set those yourself. To determine that:

  1. Source the script in your shell (the fish equivalent if applicable).
  2. Run echo "$PATH" | tr ':' '\n' | grep cargo (or your tool's name).
  3. Add the discovered directory to PATH as described above.

Either approach works. The manual route gives you finer control over exactly what configuration is being changed.

A note on fish_add_path

Fish's fish_add_path command simplifies adding a directory:

fish_add_path /some/directory

It's elegant, but it has drawbacks. The command sometimes updates PATH universally for all future sessions and sometimes only for the current session — the documentation isn't always clear about which will happen. Cleaning up a mistaken addition later can also be fiddly, and there isn't always a straightforward removal tool built in.