How to Check If a File Exists in Bash — 6 Safe Examples

Check if a file exists in Bash with [[ -f ]], [[ -d ]], [[ -e ]], [[ -L ]] and [[ -s ]]. Copy-paste examples plus the quoting mistakes to avoid.

Intro

Checking whether a file exists looks like a one-liner, and it is — until you hit unquoted variables, paths with spaces, broken symlinks, or directories you assumed were regular files. Bash gives you a small set of test operators (`-e`, `-f`, `-d`, `-L`, `-r`, `-w`, `-x`, `-s`) and `[[ ... ]]` to combine them safely. This guide covers the common cases — "does this file exist", "does this directory exist", "does it exist and is non-empty" — and the small set of gotchas that trip people up.

Bad

#!/usr/bin/env bash

# 1. Unquoted variable — breaks on paths with spaces or globs
file=$1
if [ -f $file ]; then
  echo "found"
fi

# 2. Using -e when you really meant "regular file" — matches directories too
if [ -e /var/log ]; then
  cat /var/log   # boom, /var/log is a directory
fi

# 3. Negating with ! outside the brackets — fragile
if ! [ -f "$file" ]; then ...; fi   # works, but easy to misread

# 4. Checking a symlink with -f and being surprised by broken links
# -f follows the link; a dangling symlink returns false even though the link exists

Good

#!/usr/bin/env bash

file=${1:?path required}

# Regular file (follows symlinks)
if [[ -f "$file" ]]; then
  echo "regular file"
fi

# Directory
if [[ -d "$file" ]]; then
  echo "directory"
fi

# Anything at this path (file, dir, socket, fifo, device...)
if [[ -e "$file" ]]; then
  echo "exists"
fi

# Symlink itself (true even for broken links)
if [[ -L "$file" ]]; then
  echo "symlink"
fi

# Exists AND is non-empty
if [[ -s "$file" ]]; then
  echo "non-empty file"
fi

# Negation reads cleanest inside [[ ]]
if [[ ! -f "$file" ]]; then
  echo "missing or not a regular file" >&2
  exit 1
fi

# Readable / writable / executable by current user
[[ -r "$file" ]] && echo "readable"
[[ -w "$file" ]] && echo "writable"
[[ -x "$file" ]] && echo "executable"

Explanation

Pick the operator that matches the question you are actually asking. `-e` is "anything exists at this path" — it returns true for files, directories, sockets, FIFOs, and devices. `-f` is the right choice when you specifically want a regular file (it follows symlinks before testing). `-d` is for directories. `-L` tests the symlink itself and is the only operator that returns true for a *broken* symlink. Always quote the path: `[[ -f "$file" ]]`, not `[ -f $file ]`. Unquoted, Bash splits the value on whitespace and expands globs, so a file named `my report.txt` or a path containing `*` silently misbehaves. Prefer `[[ ... ]]` over the older `[ ... ]` — it does not split or glob its arguments, supports `&&`/`||` directly, and reads negation as `[[ ! -f "$file" ]]`. For "exists and is non-empty", reach for `-s` instead of stat-ing the file yourself. For permission checks (`-r`, `-w`, `-x`), remember they reflect the *effective* user — running under `sudo` or as root changes the answer.

Takeaway

Use `-f` for regular files, `-d` for directories, `-L` for symlinks, `-e` for "anything at this path", and `-s` for non-empty. Always quote the path and prefer `[[ ... ]]` over `[ ... ]`. Match the operator to the question and you avoid almost every file-existence bug.

Related Rules

SC2086

Faq

Q

What is the difference between -e and -f in Bash?

A

`-e` is true if ANYTHING exists at the path (file, directory, socket, FIFO, device). `-f` is true only for a regular file, following symlinks. Use `-f` when you plan to read or cat the path, `-e` when you only need to know something is there.

Q

How do I check if a directory exists in Bash?

A

Use `[[ -d "$dir" ]]`. Quote the variable so paths with spaces work. `-d` follows symlinks — pair with `[[ -L "$dir" ]]` if you also want to detect a symlink pointing at a directory.

Q

How do I check if a file does NOT exist?

A

Use `[[ ! -f "$file" ]]`. Prefer negating inside the brackets over `! [[ -f "$file" ]]` — it reads more clearly and is the idiomatic form.

Q

Why does my check fail on paths with spaces?

A

You forgot to quote the variable. `[ -f $file ]` word-splits `$file` on spaces and passes multiple arguments to `-f`. Always write `[[ -f "$file" ]]` — the double brackets do not split, and the quotes make it safe under `[ ]` too.

Q

How do I check if a symlink is broken?

A

`[[ -L "$path" && ! -e "$path" ]]`. `-L` is true for the symlink itself even if the target is missing, and `-e` (which follows the link) is false for a broken one.