Bash Check If File Exists: Complete Guide with Examples
March 17, 2026
Checking whether a file exists before operating on it is one of the most common bash patterns — and getting it wrong causes some of the most frustrating bugs in shell scripts. Bash provides over a dozen file test operators that check not just existence but also file type, permissions, and size. This guide covers every important flag with practical examples, so you write scripts that fail gracefully instead of crashing on missing files.
1. Complete File Test Flags Reference
file="/etc/hosts"
[ -e "$file" ] # exists (any type: file, dir, symlink, etc.)
[ -f "$file" ] # exists AND is a regular file
[ -d "$file" ] # exists AND is a directory
[ -r "$file" ] # exists AND is readable by current user
[ -w "$file" ] # exists AND is writable by current user
[ -x "$file" ] # exists AND is executable by current user
[ -s "$file" ] # exists AND has size greater than zero (not empty)
[ -L "$file" ] # exists AND is a symbolic link
[ -h "$file" ] # same as -L
# Negate any test with !
[ ! -f "$file" ] # does NOT exist or is not a regular file
2. if Statements with File Tests
#!/bin/bash
config="/etc/myapp/config.yaml"
# Check regular file
if [[ -f "$config" ]]; then
echo "Config found, loading..."
else
echo "Config missing: $config" >&2
exit 1
fi
# Check directory
logdir="/var/log/myapp"
if [[ ! -d "$logdir" ]]; then
mkdir -p "$logdir"
echo "Created log directory"
fi
# Check readable
if [[ -r "$config" ]]; then
cat "$config"
else
echo "Cannot read $config — check permissions" >&2
fi
3. Combining Conditions
file="/data/input.csv"
# AND: file exists AND is readable AND is not empty
if [[ -f "$file" && -r "$file" && -s "$file" ]]; then
echo "File is ready to process"
fi
# Inline with && (short-circuit: only runs second command if first succeeds)
[[ -f "$file" ]] && process_file "$file"
# Inline with || (run on failure)
[[ -d "/tmp/workdir" ]] || mkdir -p "/tmp/workdir"
# Multiple checks at once
for f in /etc/passwd /etc/hosts /etc/resolv.conf; do
[[ -r "$f" ]] && echo "OK: $f" || echo "MISSING: $f"
done
4. Checking Directories and Symlinks
#!/bin/bash
# Directory check
dir="/var/data"
if [[ -d "$dir" ]]; then
echo "Directory exists"
else
echo "Not a directory (or doesn't exist)"
fi
# Symlink: -L checks the link itself; -f follows the link
link="/usr/local/bin/python"
if [[ -L "$link" ]]; then
target=$(readlink "$link")
echo "Symlink → $target"
if [[ -f "$link" ]]; then
echo "Target exists and is a file"
else
echo "WARNING: broken symlink"
fi
fi
5. Checking if a File is Empty
log="/var/log/app.log"
# -s is true if file exists AND has size > 0
if [[ -s "$log" ]]; then
echo "Log has content — $(wc -l < "$log") lines"
else
echo "Log is empty or missing"
fi
# Alternative: check line count
lines=$(wc -l < "$log" 2>/dev/null)
if (( lines > 0 )); then
echo "$lines lines in log"
fi
6. A Safe Script Pattern
Here's a production-ready pattern that validates all inputs before doing anything risky:
#!/bin/bash
set -euo pipefail
INPUT="$1"
OUTPUT_DIR="$2"
# Validate inputs before any processing
[[ -z "$INPUT" ]] && { echo "Usage: $0 " >&2; exit 1; }
[[ -f "$INPUT" ]] || { echo "ERROR: Input file not found: $INPUT" >&2; exit 1; }
[[ -r "$INPUT" ]] || { echo "ERROR: Cannot read: $INPUT" >&2; exit 1; }
[[ -s "$INPUT" ]] || { echo "ERROR: Input file is empty: $INPUT" >&2; exit 1; }
[[ -d "$OUTPUT_DIR" ]] || mkdir -p "$OUTPUT_DIR"
[[ -w "$OUTPUT_DIR" ]] || { echo "ERROR: Cannot write to: $OUTPUT_DIR" >&2; exit 1; }
echo "All checks passed — processing..."
# ... rest of script</code></pre>
</div>
Pair file existence checks with proper exit codes — see the bash exit codes guide for how to signal specific failure types. For reading the file once you've confirmed it exists, see bash read file line by line.
Summary
Use -f to check for regular files, -d for directories, -r/-w/-x for permissions, and -s for non-empty. Prefer [[ ]] over [ ] in Bash scripts for cleaner syntax and fewer quoting surprises. Always validate files at the start of scripts that operate on external inputs.