Bash Check If File Exists: Complete Guide with Examples

March 17, 2026

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.

Does file exist? NO Error YES Is it readable? Deny Proceed safely
Decision tree for safely opening a file: check existence, then readability before proceeding

1. Complete File Test Flags Reference

bash
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

bash
#!/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.