printenv — Print the value of an environment variable (or all of them) across all 5 shells
Equivalents in every shell
printenv PATHExternal binary from coreutils. `printenv` (no args) lists every env var like `env`; `printenv NAME` prints only `NAME`'s value (no `NAME=` prefix), exits 0 if set or 1 if unset. Multi-name form: `printenv USER HOME SHELL` prints each on its own line. Equivalent to `echo "$NAME"` for set vars BUT exits non-zero when unset (useful for scripts: `printenv DEBUG || exit 1`).
printenv PATHSame coreutils binary. Zsh-specific alternative: `print -r -- $NAME` (the `-r` is "raw", `--` ends option parsing). `print` is faster than `printenv` (builtin vs fork), at the cost of NOT distinguishing set-empty (`NAME=""`) from unset — both print an empty line. Use `printenv` when that distinction matters; `print -r --` for hot loops.
printenv PATHSame external. Fish's native alternative for env vars is `echo $NAME` (fish auto-splits on whitespace less aggressively than bash, so `echo $PATH` shows the path as one line of colon-separated values, not multi-word-split). For "test if env var is set", fish uses `set -q -x NAME; and printenv NAME` — `set -q -x` checks if it's set AND exported.
$env:PATHThe cleanest way — `$env:NAME` reads any env var as a string; `$null` if unset. For the exits-non-zero-when-unset semantic of `printenv NAME`, wrap: `if (-not $env:FOO) { exit 1 } else { Write-Output $env:FOO }`. For an enumeration with name AND value: `Get-ChildItem Env: | Where-Object Name -eq "FOO"`. To read a specific var via .NET: `[Environment]::GetEnvironmentVariable("FOO")` — same result but explicit-scope variants `("FOO", "User")` / `("FOO", "Machine")` read directly from the registry, bypassing the current-process env (handy when debugging "I set it with setx but it's not showing up").
echo %PATH%cmd-style env expansion. `echo %PATH%` prints the value; if `PATH` is unset, prints the literal string `%PATH%` (no error — different from bash where `$UNSET` becomes empty). The closer printenv-style behaviour: `set NAME` — lists every var starting with `NAME` (i.e., `set FOO` shows `FOO=bar` if set, nothing if unset; check `errorlevel` to detect). For "echo if set, else fail" semantics: `set FOO >nul 2>&1 && echo %FOO% || exit /b 1`.
Worked examples
Print the value of $HOME / $USERPROFILE
printenv HOMEprintenv HOMEprintenv HOME$env:USERPROFILEecho %USERPROFILE%Test "is this env var set" in a script
printenv DEBUG || { echo "DEBUG is unset"; exit 1; }set -q -x DEBUG; or begin; echo "DEBUG is unset"; exit 1; endif (-not $env:DEBUG) { Write-Error "DEBUG is unset"; exit 1 }if not defined DEBUG (echo DEBUG is unset & exit /b 1)Print multiple vars at once
printenv USER HOME SHELL"$env:USERNAME","$env:USERPROFILE","$env:COMSPEC"echo %USERNAME% & echo %USERPROFILE% & echo %COMSPEC%Gotchas
- `printenv` exits non-zero (typically 1) when the named var is UNSET, but exits 0 when set-to-empty (`FOO=""`). Scripts that conflate "unset" and "empty" miss the distinction. Bash builtin `[ -z "${FOO+x}" ]` is the canonical test for "completely unset, not just empty". The pwsh equivalent: `[Environment]::GetEnvironmentVariable("FOO") -eq $null` (true only for unset; `""` returns the empty string).
- cmd `echo %UNSET_VAR%` prints the LITERAL string `%UNSET_VAR%` (no error, no empty). Bash `echo $UNSET_VAR` prints empty. Script-portability trap: a cmd script ported to bash will silently silence variables; a bash script ported to cmd will print literal `$VAR` token strings into output (and downstream consumers like CSV / JSON parsers will choke).
- cmd `if defined NAME` is the correct existence test (works for set-to-empty too: `set NAME=` then `if defined NAME` is FALSE because empty-set unsets in cmd semantics). Don't use `if "%NAME%"==""` — that returns true for unset, set-to-empty, AND set-to-literal-empty-string, conflating three different states.
- pwsh `$env:NAME` is type-narrowed to `[string]` always — never `$null` if set, never `[int]`. To pass an env-derived number to a numeric API, explicit-cast: `[int]$env:PORT`. Without the cast, `5 + $env:PORT` does STRING concatenation (`"58000"`), not addition (`8005`).
- Bash `${NAME:?error message}` is a one-line "require this env var or die" primitive — exits non-zero AND prints the error to stderr if unset OR empty. The pwsh equivalent needs explicit logic: `if (-not $env:NAME) { throw "NAME is required" }`. Use the bash form in CI scripts where missing config should kill the build immediately.
WSL & PowerShell Core notes
Common tasks using printenv
- Set an environment variable persistently
Make an environment variable available in every NEW shell session (not just the current one) — for setting `JAVA_HOME`, adding to `PATH`, configuring API tokens for daily-use scripts.
- Show environment variables
List the environment variables visible to the current shell — for debugging "why isn't $FOO set", auditing what subprocesses will inherit, and discovering platform-injected variables (`$HOME`, `$PATH`, `$PSModulePath`).