Sagar.BlogArticle
All posts
All posts
Bash

File and Directory Tests in Bash

Guard your scripts against missing files, wrong permissions, and unexpected input types. A comprehensive reference to Bash file test operators.

January 23, 20265 min read
BashFilesTestsScripting

Before your script operates on a file, it should verify that the file exists, is the right type, and has the right permissions. Bash provides a full set of unary test operators for this — you've seen some of them; here's the complete picture.

File Test Operators Reference

File test operators

OperatorTrue if…
-e pathpath exists (any type)
-f pathpath is a regular file
-d pathpath is a directory
-L pathpath is a symbolic link
-r pathpath is readable
-w pathpath is writable
-x pathpath is executable (or searchable for dirs)
-s pathpath exists and is non-empty
-z pathpath exists and is zero bytes
-N pathpath has been modified since last read
-b pathpath is a block device
-c pathpath is a character device
-p pathpath is a named pipe (FIFO)
-S pathpath is a socket

Comparison Between Two Files

Two-file tests

OperatorTrue if…
file1 -nt file2file1 is newer than file2
file1 -ot file2file1 is older than file2
file1 -ef file2file1 and file2 are the same file (same inode)

Practical Patterns

# Guard: require a file to exist
function require_file {
    local file="$1"
    if [[ ! -f "$file" ]]; then
        echo "ERROR: Required file not found: $file" >&2
        exit 1
    fi
}

# Guard: require a directory, create if missing
function ensure_dir {
    local dir="$1"
    if [[ ! -d "$dir" ]]; then
        mkdir -p "$dir" || { echo "Cannot create: $dir" >&2; exit 1; }
    fi
}

# Check readable before processing
if [[ -f "$config" && -r "$config" ]]; then
    source "$config"
else
    echo "Cannot read config: $config" >&2
    exit 1
fi

# Check file is non-empty
if [[ -s "$logfile" ]]; then
    echo "$logfile has content"
else
    echo "$logfile is empty (or missing)"
fi

# Only process files newer than reference
for f in *.log; do
    [[ "$f" -nt last_processed.stamp ]] || continue
    process_log "$f"
done
touch last_processed.stamp

Checking Permissions Safely

script="deploy.sh"

# Check executable before running
if [[ -x "$script" ]]; then
    ./"$script"
else
    echo "$script is not executable — run: chmod +x $script" >&2
    exit 1
fi

# Check write permissions on a directory
if [[ ! -w "/var/log/myapp" ]]; then
    echo "Cannot write to log directory" >&2
    exit 1
fi

# Validate a symlink target exists
link="current"
if [[ -L "$link" ]]; then
    target=$(readlink -f "$link")
    if [[ -e "$target" ]]; then
        echo "Symlink $link$target (valid)"
    else
        echo "Symlink $link$target (BROKEN)" >&2
    fi
fi
Quick Check

What is the difference between `-e` and `-f` file tests?

Exercise

Write a function safe_copy that:

  1. Takes source and destination paths as arguments
  2. Checks that source exists and is readable — exit with error if not
  3. Checks that the destination directory is writable — exit with error if not
  4. If the destination file already exists, asks the user to confirm overwrite
  5. Performs the copy with cp