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`).
How to show environment variables in each shell
env`env` (no args) prints all EXPORTED variables — what subprocesses will inherit. `printenv` does the same; `printenv PATH` prints one var (no `$`). `declare -p` (bash builtin) shows ALL shell variables including non-exported locals, with their declaration flags (`-x` exported, `-r` readonly, `-a` array, `-A` assoc array). `set | head` shows everything PLUS functions. For "is $FOO set vs empty": `${FOO:?undefined}` errors if unset/empty; `${FOO+set}` prints "set" if defined (even empty). For a SINGLE variable: `echo "${FOO-unset}"`.
envSame `env` / `printenv` externals as bash. Zsh-specific: `typeset` is the modernized `declare` (same semantics, friendlier name). `typeset -x` lists exported, `typeset -m "FOO*"` matches by pattern. `printenv -0` separates values with NUL bytes — safe for values containing newlines (which `env` output mangles). The `(t)` parameter expansion flag inspects type: `echo ${(t)PATH}` → `scalar-export`. To see ONLY exported with values that contain spaces correctly: `env -0 | sort -z | tr '\0' '\n'`.
setFish `set` (no args) lists EVERY variable — local, global, universal — with no quotes. `set -x` lists exported only (subprocess-visible). `set --show FOO` (or `set -S FOO`) shows scope + value for one var — fish's unique view across local/global/universal scopes. Universal variables (`set -U`) persist across sessions, stored in `~/.config/fish/fish_variables`. To distinguish "exported vs not": `set --show` adds an `(exported)` annotation. NO `printenv` builtin — but the system `printenv` binary works.
Get-ChildItem env:The pwsh-native answer: `env:` is a PSDrive. `Get-ChildItem env:` (or `ls env:` / `dir env:`) lists name=value. For one variable: `$env:PATH` (note the `$` prefix and `:` — neither `$env.PATH` nor `$env::PATH`). The pwsh `env:` drive is **process-scope only** — it reflects what THIS pwsh inherited at launch + any in-session changes. To see what NEW pwsh sessions will inherit: `[Environment]::GetEnvironmentVariables("User")` (per-user registry vars) or `("Machine")` (system-wide registry vars). The 3-scope distinction (Process / User / Machine) is the #1 newcomer surprise — `$env:FOO = "x"` only sets Process scope; it vanishes when the session ends.
set`set` (no args) prints all env vars as `NAME=value`. For one variable: `echo %FOO%` (or `set FOO` to print all that start with `FOO`). `set | findstr /B "PATH="` filters to vars starting with `PATH=`. `setx` (which WRITES persistently) is NOT the read tool — `set` is. To compare User vs System scope (the registry-stored persistent values that NEW cmd / pwsh sessions will inherit): `reg query "HKCU\Environment"` and `reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"`. `wmic environment list brief` lists both scopes in one shot (but `wmic` is deprecated in Windows 11; use pwsh `[Environment]::GetEnvironmentVariables("User"|"Machine")`).
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- **Exported vs all variables**: `env` / `printenv` show what subprocesses will inherit (exported only). Shell-local variables (set without `export`) are INVISIBLE to children but visible to the current shell. `declare -p` (bash) / `typeset` (zsh) / `set` (fish, cmd) shows EVERYTHING. The debugging idiom for "my child process can't see $FOO" is: run `env | grep FOO` — if absent, you forgot `export`. The corollary for pwsh: `$env:FOO` is always process-exported (no separate "shell-local" concept); the unset case is the only failure mode.
- **Secret leakage warning**: `env` / `set` output frequently includes API tokens (`AWS_SECRET_ACCESS_KEY`, `GITHUB_TOKEN`, `OPENAI_API_KEY`, etc). DO NOT paste raw output to chat, paste-bin, support ticket, or log. Always pre-filter: `env | grep -v -iE "TOKEN|KEY|SECRET|PASSWORD"` (bash); `Get-ChildItem env: | Where-Object Name -notmatch "TOKEN|KEY|SECRET|PASSWORD"` (pwsh). The corollary: in CI logs, NEVER `printenv` or `set` unfiltered — GitHub Actions / GitLab CI auto-mask known secret values but only those registered as secrets, NOT arbitrary env-injected tokens.
- **Pwsh 3-scope model**: `$env:FOO` = Process scope (lives until shell exits, NOT inherited by new pwsh sessions). `[Environment]::GetEnvironmentVariable("FOO","User")` = User scope (persisted in `HKCU\Environment`, inherited by all new sessions for this user). `[Environment]::GetEnvironmentVariable("FOO","Machine")` = Machine scope (persisted in `HKLM`, inherited by all sessions for all users, requires admin to set). Linux/macOS pwsh ONLY has Process scope (no registry); the User/Machine APIs return null. The pwsh-on-Linux convention is to put persistent vars in `~/.config/powershell/profile.ps1` — equivalent to bash `~/.bashrc`.
- For "what env vars does THIS specific process see" (not your shell), Linux: `cat /proc/<PID>/environ | tr '\0' '\n'` (NUL-separated, one per line). macOS: `ps eww <PID>` (`e` shows env, `ww` unwraps). Useful for debugging "this daemon launched at boot doesn't see my $LANG setting" — the daemon's env is the boot-time env, NOT your shell's. pwsh: `(Get-Process -Id <PID>).StartInfo.EnvironmentVariables` works only for processes pwsh launched itself; reading another process's env requires elevated privileges + WMI.