Prepend a directory to PATH
Add a directory to the FRONT of PATH so commands inside it override system equivalents — for tool-version pinning (node@18 vs system node) and local-bin priorities.
How to prepend a directory to path in each shell
export PATH="/opt/bin:$PATH"Session-only. Persist by appending the same line to `~/.bashrc` (login + interactive shells) or `~/.bash_profile` (login shells on macOS). The `:` separator is Unix-specific.
export PATH="/opt/bin:$PATH"Persist in `~/.zshrc`. Zsh-specific: the `$path` ARRAY (lowercase) is tied to `$PATH` (uppercase) via `typeset -T` — `path=(/opt/bin $path)` is the zsh-idiomatic prepend (no string-splitting on `:`).
set -Ux fish_user_paths /opt/bin $fish_user_paths`-U` = universal (persists across sessions, stored in `~/.config/fish/fish_variables`). `-x` = exported to child processes. `fish_user_paths` is the canonical fish PATH variable — fish auto-prepends it to PATH. NO `.config/fish/config.fish` edit needed.
$env:Path = "/opt/bin" + [IO.Path]::PathSeparator + $env:PathSession-only. Persist via `[Environment]::SetEnvironmentVariable("Path", "/opt/bin;$($env:Path)", "User")` (Windows registry; takes effect on next session). `[IO.Path]::PathSeparator` is `;` on Windows, `:` on Linux/macOS — cross-platform pwsh scripts MUST use this constant, not a literal.
set "PATH=C:\opt\bin;%PATH%"Session-only. Persistent machine-wide: `setx PATH "C:\opt\bin;%PATH%" /M` (needs admin). User-only: `setx PATH "C:\opt\bin;%PATH%"`. WARNING: `setx` truncates at 1024 chars — long PATHs get silently corrupted; use the registry or pwsh `[Environment]::SetEnvironmentVariable` instead.
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- **Separator divergence**: `:` on Linux/macOS/WSL, `;` on Windows. A pwsh 7 script running on macOS uses `:`; the same script on Windows uses `;`. Hardcoding either breaks cross-platform pwsh — `[IO.Path]::PathSeparator` is THE portable accessor. Likewise `[IO.Path]::DirectorySeparatorChar` (`/` Unix, `\` Windows) and `[IO.Path]::Combine(a, b, c)` to build paths without manual separators. For bash/zsh scripts that need cross-platform: shell-out to `python -c "import os; print(os.pathsep)"` or detect via `case $(uname) in MINGW*|MSYS*|CYGWIN*) sep=\";\";; *) sep=\":\";; esac` (double-quoted shell strings to avoid case-branch ambiguity).
- Prepend vs append: prepending makes your custom binary WIN over the system one (`/opt/node@18/bin/node` over `/usr/bin/node`). Appending makes it a fallback (used only if not found earlier). Almost always you want prepend — and tools like `nvm`, `rbenv`, `pyenv`, and `direnv` work by prepending their version-specific shim dirs. If a `which node` returns the wrong version, almost always the answer is "PATH ordering" — `echo $PATH | tr ":" "\n"` to inspect.
- **Idempotency** — adding the same dir twice on every shell start bloats PATH. A 200-entry PATH slows down EVERY external command (each exec walks PATH linearly). Idempotent prepend: `case ":$PATH:" in *":/opt/bin:"*) ;; *) export PATH="/opt/bin:$PATH" ;; esac` (the `:...:` wrapping makes the substring check anchored — `/opt/bin` matches at any position but only with explicit `:` boundaries, so `/opt/bin` does NOT match `/opt/bing`). pwsh: `if ($env:Path -notlike "*$newDir*") { $env:Path = "$newDir;$env:Path" }`.
- **Persistence** is per-shell: bash uses `~/.bashrc` (interactive) and `~/.bash_profile` (login — and on macOS, every Terminal.app window is a login shell). zsh uses `~/.zshrc` (interactive) + `~/.zshenv` (every shell, even non-interactive). fish uses `~/.config/fish/config.fish` for one-shot init, but `set -U` variables PERSIST WITHOUT a config.fish edit (stored in `fish_variables`). pwsh uses `$PROFILE` (different file per host — `$PROFILE.CurrentUserAllHosts` for all). Windows env vars (`setx` / `[Environment]::SetEnvironmentVariable`) persist in the registry; existing sessions DON'T see the change — only new processes spawned after.
Related commands
Related tasks
- Source an env file into the current shell— Load variables from a `.env`-style file into the current shell session — for project-specific secrets, dev-mode flags, and tool-version locks.
- Show the file path of a command— Print where on the filesystem a command resolves to — for debugging PATH issues, version mismatches, and "which python is this".
- Detect the current shell— Determine which shell (bash / zsh / fish / pwsh / cmd) is currently running — for conditional dotfile loading and script-detection scenarios.
- List all shell aliases— Print every alias currently defined in the shell — for debugging "why does `ls` behave differently here" and auditing dotfiles.