test-path — Check whether a path exists — bash [ -e ] equivalent across all 5 shells
Equivalents in every shell
[ -e /etc/hosts ]`[ -e path ]` is "exists, of any type"; `-f` is "regular file"; `-d` is "directory"; `-L` is "symbolic link" (does not follow); `-r`/`-w`/`-x` are read/write/execute permission checks. The bracketed form is a builtin (POSIX `test`), invoked from `if` / `&&` / `||`. `[[ -e path ]]` (bash/zsh extended test) is similar with no word-splitting.
[[ -e /etc/hosts ]]Same POSIX-flag set on `[[ ]]` (bash/zsh extended test) plus zsh-only: `-N` "modified after access" (a less-known classic). `[[ ]]` does not split words / glob, so quoting is forgiving compared to `[ ]`. zsh also exposes `(.)` (regular files only), `(/)` (directories only) glob qualifiers — `print -l *(.)` is "all regular files in cwd" without an explicit test.
test -e /etc/hosts`test` is fish-builtin (NOT the external `/usr/bin/test`) and supports the same POSIX flags. There is no `[[ ]]` in fish. Combine via fish boolean operators: `test -e $f; and test -r $f`. Fish does NOT do bash-style short-circuit (`&&` / `||`) — use `and` / `or` keywords.
Test-Path /etc/hostsPowerShell-native cmdlet. Returns `$true`/`$false` (a real bool, not an exit code). `-PathType Container` checks "is it a directory"; `-PathType Leaf` is "regular file". `-NewerThan` and `-OlderThan` accept DateTime for time-based existence tests. Works across PS providers — `Test-Path HKLM:\SOFTWARE\MyApp` checks a registry key.
if exist C:\Windows\System32\notepad.exe (echo yes)`if exist <path>` is the only built-in. Trailing `\` (`if exist C:\Windows\`) tests for a directory — the only way to differentiate file-vs-directory in cmd. There is no permission test; `dir <path>` and a swallowed errorlevel is the workaround for "is it readable". Wildcards work: `if exist *.log (echo found)`.
Worked examples
Check if a file exists
[ -f /etc/hosts ] && echo found[[ -f /etc/hosts ]] && echo foundtest -f /etc/hosts; and echo foundif (Test-Path /etc/hosts -PathType Leaf) { "found" }if exist C:\Windows\notepad.exe echo foundCheck if a directory exists
[ -d ~/.config ] && echo dirif (Test-Path ~/.config -PathType Container) { "dir" }if exist C:\Users\me\ echo dirCheck if any file matches a glob
compgen -G "*.log" > /dev/null && echo "logs present"if (Test-Path "*.log") { "logs present" }if exist *.log echo logs presentGotchas
- `Test-Path path` returns `$true` for BOTH files AND directories by default — explicit `-PathType Leaf` or `-PathType Container` is required to distinguish them. The bash `[ -e path ]` has the same any-type semantics, but `[ -f ]` (file only) and `[ -d ]` (dir only) are short and habitual, so people forget to add `-PathType` to PS code. Caused production bugs where `Test-Path config.json` was true for a *directory* called `config.json`.
- PowerShell wildcards on `Test-Path` (`Test-Path *.log`) return `$true` if ANY match exists — convenient. But `Test-Path '['` (a literal `[` character) FAILS because `[` is a wildcard metacharacter. Use `-LiteralPath` to disable wildcard interpretation: `Test-Path -LiteralPath '[brackets].txt'`. Same gotcha applies to filenames with `]`, `*`, `?`.
- `[ -e /broken/symlink ]` is FALSE if the symlink target does not exist (it follows the link). `[ -L /broken/symlink ]` is TRUE (does not follow). Production scripts that clean up dangling symlinks must use `-L`, not `-e` — the bug is `-e` quietly skipping the broken link instead of removing it.
- fish `test -e $var` with an UNSET variable gives an error (`test: Missing argument`), not false — fish does not auto-promote unset to empty string the way bash does. Quote the variable to force empty-string-on-unset: `test -e "$var"`. POSIX `[ -e "$var" ]` (with the quotes) handles this; without quotes, both shells word-split.
- cmd `if exist` evaluates trailing-`\` as the directory check — `if exist C:\Windows\System32` is true even though System32 is a DIRECTORY (no trailing slash treats as 'name exists'). To force file-only check, `if exist <path> ( if not exist <path>\NUL ( echo it is a file ))` — the `\NUL` device trick is the only built-in file-vs-dir distinction. PowerShell's `-PathType Leaf` is dramatically cleaner.