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
| Operator | True if… |
|---|---|
-e path | path exists (any type) |
-f path | path is a regular file |
-d path | path is a directory |
-L path | path is a symbolic link |
-r path | path is readable |
-w path | path is writable |
-x path | path is executable (or searchable for dirs) |
-s path | path exists and is non-empty |
-z path | path exists and is zero bytes |
-N path | path has been modified since last read |
-b path | path is a block device |
-c path | path is a character device |
-p path | path is a named pipe (FIFO) |
-S path | path is a socket |
Comparison Between Two Files
Two-file tests
| Operator | True if… |
|---|---|
file1 -nt file2 | file1 is newer than file2 |
file1 -ot file2 | file1 is older than file2 |
file1 -ef file2 | file1 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.stampChecking 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
fiQuick Check
What is the difference between `-e` and `-f` file tests?
Exercise
Write a function safe_copy that:
- Takes source and destination paths as arguments
- Checks that source exists and is readable — exit with error if not
- Checks that the destination directory is writable — exit with error if not
- If the destination file already exists, asks the user to confirm overwrite
- Performs the copy with
cp