Skip to content
shellmap

Check if a file exists

Test whether a file (or directory, or symlink) exists at a path before reading, writing, or branching script logic.

How to check if a file exists in each shell

Bashunix
[ -f file.txt ] && echo exists

`[ -f file ]` is "exists AND is a regular file" — fails for directories, sockets, devices. Different tests for different kinds: `[ -e file ]` "exists at all" (any type, including dirs/symlinks/devices); `[ -d dir ]` directory only; `[ -L link ]` is a symlink (whether target exists or not); `[ -s file ]` exists AND size > 0; `[ -r file ]` exists AND readable; `[ -w file ]` writable; `[ -x file ]` executable. Use `[[ ]]` for safer bash-specific test (handles unquoted vars / empty strings without word-splitting bugs). Negate with `!`: `[ ! -f file ]`.

Zshunix
[ -f file.txt ] && echo exists

Same `test` builtin and flags as bash. Zsh adds glob qualifiers as a more powerful alternative: `*.log(N)` expands to matching files OR empty if none (`N` = nullglob qualifier). `*.log(.)` only regular files. `*.log(.L+1m)` files > 1MB. For "does at least one match exist": `setopt null_glob; files=(*.log); (( ${#files} )) && echo "some logs exist"`.

Fishunix
test -f file.txt; and echo exists

Fish uses `test` directly (no `[` alias). Chain conditionals with `and` / `or` instead of `&&` / `||` (fish quirk). Same flag set as bash: `-f`, `-e`, `-d`, `-L`, `-s`, `-r`, `-w`, `-x`. For multiple files in one shot, fish's native loop is cleaner than bash: `for f in file1 file2 file3; test -f $f; or echo "missing: $f"; end`.

PowerShellwindows
Test-Path file.txt -PathType Leaf

`Test-Path` is the canonical pwsh check. `-PathType Leaf` is "exists AND is a file" (the `-f` equivalent); `-PathType Container` is "directory"; omit `-PathType` for "anything". Returns `$True` / `$False` — usable directly in `if`: `if (Test-Path file.txt -PathType Leaf) { ... }`. For wildcards: `Test-Path *.log` returns `$True` if ANY match. For "exists and non-empty": `(Get-Item file.txt -ErrorAction SilentlyContinue).Length -gt 0`. Symlink-specific: `(Get-Item file.txt).LinkType -eq "SymbolicLink"` (Windows 10+).

cmd.exewindows
if exist file.txt echo exists

`if exist` is the cmd primitive — works directly inline, no `[` / `test`. Quirks: NO quoting required for paths even with spaces (the parser handles it), trailing wildcard works (`if exist *.log echo found`), and "directory exists" needs a trailing path component: `if exist C:\Users\NUL echo directory-exists` (the `NUL` device exists in every directory — the canonical cmd trick for testing dir existence vs file). Negate with `if not exist`. No native size / permission tests — shell out to pwsh for those.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **TOCTOU race**: "check then use" is fundamentally racy. `if [ -f file ]; then cat file; fi` can fail if the file disappears between the check and the `cat`. For exclusive create: `set -o noclobber; > file` (errors if file exists). For atomic open-or-create with a lock: `flock`. For "create file ONLY if it doesn't exist": `(set -C; > file) 2>/dev/null && echo created || echo existed`. The check-and-use pattern is fine for non-security-critical control flow, dangerous for anything where a malicious user could swap the file in between.
  • The difference between `-e` (exists, anything) and `-f` (exists AND regular file) is the #1 source of bugs in deploy scripts. A script checking `[ -f /var/lib/myapp/config ]` will silently SKIP if someone changes that path to a directory (because `-f` rejects non-files) or if the symlink target disappears (`-f` follows links and fails on broken ones). Use `-e` when you want "anything is there", `-f` when you specifically need a real file, and `-L` to detect symlinks even if their target is missing.
  • Pwsh `Test-Path` defaults to no error on bad paths (returns `$False`). Different from `Get-Item`, which throws if the file is missing — `Get-Item file.txt -ErrorAction SilentlyContinue` is the silent variant. For pipeline-style: `Get-Item file.txt -ErrorAction Ignore | ForEach-Object { ... }` only enters the loop body if the file exists.
  • For "wait until a file appears" (common in CI / Docker entrypoints): `until [ -f /tmp/ready ]; do sleep 1; done` (bash/zsh), `while not test -f /tmp/ready; sleep 1; end` (fish), `while (-not (Test-Path /tmp/ready)) { Start-Sleep 1 }` (pwsh), `:loop` + `if not exist C:\tmp\ready (timeout 1 >nul & goto loop)` (cmd). For "wait until a file is removed", flip the test.

Related commands

Related tasks