shell-scripting

How to Check if a File Exists in Bash

Use Bash conditionals to check whether a file exists before reading, writing, or deleting it. The most reliable approach combines the test command (or its bracket alias [ ] ) wi...

Mara Ellison
How to Check if a File Exists in Bash

Use Bash conditionals to check whether a file exists before reading, writing, or deleting it. The most reliable approach combines the test command (or its bracket alias [ ]) with existence flags such as -e (any file) and -f (regular file only). Bash’s newer [[ ]] construct adds safer quoting and pattern matching, reducing edge-case errors in scripts. This guide explains syntax, pitfalls, and production-grade patterns for everyday workflows and automation.

Quick answer

Check existence with -e and regular files with -f, using either [ ] or [[ ]]. Always quote variables and prefer [[ ]] in Bash to avoid word splitting and pathname expansion issues. Example: [[ -e "$file" ]] && echo "exists". These patterns are deterministic, portable across POSIX shells and Bash, and safe in scripts.

Primary file tests for existence

  • -e "$path": true if path exists (any type)
  • -f "$path": true if path exists and is a regular file
  • -d "$path": true if path exists and is a directory
  • -b "$path", -c "$path", -p "$path": true for block/char devices and named pipes

Using test, [ ], and [[ ]] for existence checks

Syntax with test

The test builtin evaluates expressions without requiring a closing token. Example: test -e "$path" && echo exists. It behaves identically to [ ] but uses a different invocation style.

Using [ ] (Bourne-style)

[ ] is a synonym for test and requires a closing ]. Quote variables to prevent word splitting and pathname expansion. Example: [ -e "$path" ] && echo exists. This form is POSIX and available in all Bourne-derived shells, including Bash.

Using [[ ]] (Bash-builtin)

[[ ]] is a Bash keyword with safer semantics. It does not perform filename expansion on unquoted words and handles variables robustly. Example: [[ -e "$path" ]] && echo exists. This is the recommended style in Bash scripts for clarity and safety.

Practical examples and common patterns

Use existence checks to avoid errors when reading configuration, processing user input, or managing temporary artifacts. Below are concise, copy-paste patterns you can adapt.

Check and report

file="/path/to/file"
if [[ -e "$file" ]]; then
  echo "File exists: $file"
else
  echo "File not found: $file"
fi

Fail early if missing

input="${1:-$HOME/data/input.txt}"
[[ -e "$input" ]] || { echo "Missing: $input" >&2; exit 1; }

Differentiate file vs directory

path="$1"
if [[ -f "$path" ]]; then
  echo "Regular file"
elif [[ -d "$path" ]]; then
  echo "Directory"
else
  echo "Exists but not a regular file or directory"
fi

Behavioral differences and limitations

While -e and -f cover most cases, keep these behaviors in mind. Symbolic links are followed by -e; use -L to test the link itself. On network filesystems, metadata may be briefly inconsistent, so design scripts to handle missing or changing files gracefully. Neither flag guarantees read or write permission; use -r and -w when permissions matter.

Portable best practices

  • Quote variables: use "$path" to avoid word splitting and pathname expansion.
  • Prefer [[ ]] in Bash for safer evaluation and fewer quoting surprises.
  • When POSIX portability is required, use [ ] and maintain consistency.
  • Combine checks with permission tests (-r, -w) when access control matters.
  • Handle race conditions by assuming files can change between test and action; prefer atomic operations when possible.

Summary table

Expression True when… Notes
[[ -e "$path" ]] Any type exists (file, dir, symlink, etc.) Bash-safe; respects quoted variables
[[ -f "$path" ]] Regular file exists Follows symlinks by default
[[ -d "$path" ]] Directory exists Use to distinguish directories
[[ -L "$path" ]] Symbolic link exists Tests the link itself, not the target