πŸ“ Bash File Testing & Attributes

Comprehensive Reference Guide β€” File tests, permissions, stat command, timestamps, and file type detection

πŸ“‹ Table of Contents

Overview

Bash provides extensive file testing capabilities through test operators ([ ] and [[ ]]) and the stat command. These tools let you check file existence, type, permissions, ownership, size, and timestampsβ€”essential for writing robust scripts that handle files safely.

Key Capabilities

Existence & Type: Check if files exist and what type they are (regular, directory, symlink, etc.)

Permissions: Test read, write, execute permissions and special bits

Comparison: Compare files by modification time and identity

Attributes: Get detailed metadata with stat

Complete File Test Operators

Existence and Type Tests

TestDescriptionExample
-e file File exists (any type) [[ -e /etc/passwd ]]
-f file Regular file exists [[ -f config.txt ]]
-d file Directory exists [[ -d /var/log ]]
-L file Symbolic link exists [[ -L /usr/bin/python ]]
-h file Symbolic link (same as -L) [[ -h link ]]
-b file Block device exists [[ -b /dev/sda ]]
-c file Character device exists [[ -c /dev/tty ]]
-p file Named pipe (FIFO) exists [[ -p /tmp/mypipe ]]
-S file Socket exists [[ -S /var/run/docker.sock ]]

Permission Tests

TestDescriptionExample
-r file File is readable [[ -r file.txt ]]
-w file File is writable [[ -w file.txt ]]
-x file File is executable [[ -x script.sh ]]
-u file Set-user-ID bit set [[ -u /usr/bin/passwd ]]
-g file Set-group-ID bit set [[ -g file ]]
-k file Sticky bit set [[ -k /tmp ]]
-O file Owned by effective UID [[ -O myfile ]]
-G file Owned by effective GID [[ -G myfile ]]

Size and Content Tests

TestDescriptionExample
-s file File exists and size > 0 [[ -s data.txt ]]
-t fd File descriptor is terminal [[ -t 0 ]] (stdin)

Comparison Tests

TestDescriptionExample
file1 -nt file2 file1 newer than file2 [[ a.txt -nt b.txt ]]
file1 -ot file2 file1 older than file2 [[ a.txt -ot b.txt ]]
file1 -ef file2 Same device and inode (hard link) [[ a -ef b ]]
⚠️ [ ] vs [[ ]]
Prefer [[ ]] for file testsβ€”it handles spaces in filenames better and doesn't require quoting variables. [ ] is POSIX but more error-prone.

The stat Command

The stat command provides detailed file attributes beyond what test operators offer.

Common stat Format Sequences

FormatDescriptionExample Output
%nFile namemyfile.txt
%sSize in bytes1234
%bBlocks allocated8
%BBlock size512
%FFile typeregular file
%aOctal permissions644
%AHuman permissions-rw-r--r--
%uOwner UID1000
%UOwner namecraig
%gGroup GID1000
%GGroup namecraig
%hHard link count1
%iInode number12345678
%XAccess time (epoch)1731958245
%YModify time (epoch)1731958245
%ZChange time (epoch)1731958245
%WBirth time (epoch)1731958245
%xAccess time (human)2025-11-18 14:30:45
%yModify time (human)2025-11-18 14:30:45
%zChange time (human)2025-11-18 14:30:45

Basic stat Usage

# Default output
stat filename

# Specific format
stat -c '%s' filename        # Size only
stat -c '%a %U:%G' filename  # Permissions and owner
stat -c '%Y' filename        # Mtime as epoch

# Multiple files
stat -c '%n: %s bytes' *.txt

Detailed Examples

1
Basic File Existence and Type Checking

Essential tests for checking if files exist and what type they are.

#!/bin/bash
# Basic file existence and type checking

# Create test files for demonstration
mkdir -p /tmp/test_files
touch /tmp/test_files/regular.txt
mkdir /tmp/test_files/directory
ln -sf /tmp/test_files/regular.txt /tmp/test_files/symlink
mkfifo /tmp/test_files/pipe 2>/dev/null

echo "=== File Existence Tests ==="

# Test if file exists (any type)
file="/tmp/test_files/regular.txt"
if [[ -e "$file" ]]; then
    echo "$file exists"
else
    echo "$file does not exist"
fi

# Test for non-existent file
file="/tmp/test_files/nonexistent"
if [[ ! -e "$file" ]]; then
    echo "$file does not exist"
fi

echo ""
echo "=== File Type Tests ==="

test_type() {
    local path=$1
    local name=$(basename "$path")
    
    if [[ -f "$path" ]]; then
        echo "$name: regular file"
    elif [[ -d "$path" ]]; then
        echo "$name: directory"
    elif [[ -L "$path" ]]; then
        echo "$name: symbolic link"
    elif [[ -p "$path" ]]; then
        echo "$name: named pipe"
    elif [[ -b "$path" ]]; then
        echo "$name: block device"
    elif [[ -c "$path" ]]; then
        echo "$name: character device"
    elif [[ -S "$path" ]]; then
        echo "$name: socket"
    else
        echo "$name: unknown type"
    fi
}

for item in /tmp/test_files/*; do
    test_type "$item"
done

echo ""
echo "=== Check Before Operations ==="

# Safe file creation
file="/tmp/test_files/newfile.txt"
if [[ -e "$file" ]]; then
    echo "File already exists: $file"
else
    touch "$file"
    echo "Created: $file"
fi

# Safe directory creation
dir="/tmp/test_files/newdir"
if [[ -d "$dir" ]]; then
    echo "Directory already exists: $dir"
else
    mkdir "$dir"
    echo "Created directory: $dir"
fi

echo ""
echo "=== Handle Symlinks Carefully ==="

# -L tests the link itself, -f follows it
link="/tmp/test_files/symlink"

if [[ -L "$link" ]]; then
    echo "$link is a symlink"
    
    # Check if target exists
    if [[ -e "$link" ]]; then
        echo "  Target exists: $(readlink -f "$link")"
    else
        echo "  Broken link: target doesn't exist"
    fi
fi

# -f follows symlink
if [[ -f "$link" ]]; then
    echo "$link points to a regular file"
fi

echo ""
echo "=== Practical: Config File Check ==="

check_config() {
    local config=$1
    
    if [[ ! -e "$config" ]]; then
        echo "ERROR: Config not found: $config"
        return 1
    fi
    
    if [[ -d "$config" ]]; then
        echo "ERROR: Config is a directory: $config"
        return 1
    fi
    
    if [[ ! -f "$config" ]]; then
        echo "ERROR: Config is not a regular file: $config"
        return 1
    fi
    
    echo "OK: Config file valid: $config"
    return 0
}

check_config "/etc/passwd"
check_config "/tmp"
check_config "/nonexistent"

# Cleanup
rm -rf /tmp/test_files
Output: === File Existence Tests === /tmp/test_files/regular.txt exists /tmp/test_files/nonexistent does not exist === File Type Tests === directory: directory pipe: named pipe regular.txt: regular file symlink: symbolic link === Check Before Operations === Created: /tmp/test_files/newfile.txt Created directory: /tmp/test_files/newdir === Handle Symlinks Carefully === /tmp/test_files/symlink is a symlink Target exists: /tmp/test_files/regular.txt /tmp/test_files/symlink points to a regular file === Practical: Config File Check === OK: Config file valid: /etc/passwd ERROR: Config is a directory: /tmp ERROR: Config not found: /nonexistent
2
Permission Testing

Checking read, write, execute permissions and special bits.

#!/bin/bash
# Permission testing

# Create test files
mkdir -p /tmp/perm_test
touch /tmp/perm_test/readable.txt
chmod 444 /tmp/perm_test/readable.txt

touch /tmp/perm_test/writable.txt
chmod 666 /tmp/perm_test/writable.txt

touch /tmp/perm_test/executable.sh
chmod 755 /tmp/perm_test/executable.sh

touch /tmp/perm_test/noaccess.txt
chmod 000 /tmp/perm_test/noaccess.txt

echo "=== Basic Permission Tests ==="

test_perms() {
    local file=$1
    local name=$(basename "$file")
    
    printf "%-20s " "$name:"
    
    [[ -r "$file" ]] && printf "r" || printf "-"
    [[ -w "$file" ]] && printf "w" || printf "-"
    [[ -x "$file" ]] && printf "x" || printf "-"
    
    echo ""
}

for f in /tmp/perm_test/*; do
    test_perms "$f"
done

echo ""
echo "=== Permission Checks Before Operations ==="

# Check before reading
file="/tmp/perm_test/readable.txt"
if [[ -r "$file" ]]; then
    echo "Can read $file"
    # cat "$file"
else
    echo "Cannot read $file"
fi

# Check before writing
file="/tmp/perm_test/noaccess.txt"
if [[ -w "$file" ]]; then
    echo "Can write to $file"
else
    echo "Cannot write to $file (no permission)"
fi

# Check before executing
file="/tmp/perm_test/executable.sh"
if [[ -x "$file" ]]; then
    echo "Can execute $file"
else
    echo "Cannot execute $file"
fi

echo ""
echo "=== Special Permission Bits ==="

# Check setuid
if [[ -u /usr/bin/passwd ]]; then
    echo "/usr/bin/passwd has setuid bit"
fi

# Check sticky bit on /tmp
if [[ -k /tmp ]]; then
    echo "/tmp has sticky bit"
fi

# Check setgid
check_setgid() {
    local file=$1
    if [[ -g "$file" ]]; then
        echo "$file has setgid bit"
    else
        echo "$file does not have setgid bit"
    fi
}

echo ""
echo "=== Ownership Tests ==="

# Check if current user owns file
file="/tmp/perm_test/readable.txt"
if [[ -O "$file" ]]; then
    echo "You own $file"
fi

# Check if current group owns file
if [[ -G "$file" ]]; then
    echo "Your group owns $file"
fi

echo ""
echo "=== Practical: Safe File Operations ==="

safe_write() {
    local file=$1
    local content=$2
    
    # Check parent directory
    local dir=$(dirname "$file")
    if [[ ! -d "$dir" ]]; then
        echo "ERROR: Directory doesn't exist: $dir"
        return 1
    fi
    
    if [[ ! -w "$dir" ]]; then
        echo "ERROR: Cannot write to directory: $dir"
        return 1
    fi
    
    # Check file if exists
    if [[ -e "$file" ]]; then
        if [[ ! -w "$file" ]]; then
            echo "ERROR: File not writable: $file"
            return 1
        fi
        if [[ -d "$file" ]]; then
            echo "ERROR: Is a directory: $file"
            return 1
        fi
    fi
    
    echo "$content" > "$file"
    echo "OK: Written to $file"
    return 0
}

safe_write "/tmp/perm_test/output.txt" "Hello World"
safe_write "/tmp/perm_test/noaccess.txt" "Should fail"

echo ""
echo "=== Practical: Script Self-Check ==="

script_check() {
    local script=$1
    
    if [[ ! -f "$script" ]]; then
        echo "ERROR: Script not found"
        return 1
    fi
    
    if [[ ! -r "$script" ]]; then
        echo "ERROR: Script not readable"
        return 1
    fi
    
    if [[ ! -x "$script" ]]; then
        echo "WARNING: Script not executable"
        echo "  Run: chmod +x $script"
    fi
    
    echo "Script checks passed"
    return 0
}

# Cleanup
rm -rf /tmp/perm_test
Output: === Basic Permission Tests === executable.sh: rwx noaccess.txt: --- readable.txt: r-- writable.txt: rw- === Permission Checks Before Operations === Can read /tmp/perm_test/readable.txt Cannot write to /tmp/perm_test/noaccess.txt (no permission) Can execute /tmp/perm_test/executable.sh === Special Permission Bits === /usr/bin/passwd has setuid bit /tmp has sticky bit === Ownership Tests === You own /tmp/perm_test/readable.txt Your group owns /tmp/perm_test/readable.txt === Practical: Safe File Operations === OK: Written to /tmp/perm_test/output.txt ERROR: File not writable: /tmp/perm_test/noaccess.txt
3
File Size and Content Tests

Testing file size, checking for empty files, and size comparisons.

#!/bin/bash
# File size and content tests

# Create test files
mkdir -p /tmp/size_test
echo "Hello World" > /tmp/size_test/small.txt
dd if=/dev/zero of=/tmp/size_test/large.bin bs=1M count=5 2>/dev/null
touch /tmp/size_test/empty.txt
printf "No newline" > /tmp/size_test/nonewline.txt

echo "=== Empty File Test (-s) ==="

for f in /tmp/size_test/*; do
    name=$(basename "$f")
    if [[ -s "$f" ]]; then
        echo "$name: has content"
    else
        echo "$name: empty"
    fi
done

echo ""
echo "=== Get File Sizes ==="

# Using stat
for f in /tmp/size_test/*; do
    name=$(basename "$f")
    size=$(stat -c '%s' "$f")
    printf "%-20s %10d bytes\n" "$name" "$size"
done

echo ""
echo "=== Human-Readable Sizes ==="

human_size() {
    local bytes=$1
    
    if ((bytes >= 1073741824)); then
        echo "$(echo "scale=2; $bytes/1073741824" | bc) GB"
    elif ((bytes >= 1048576)); then
        echo "$(echo "scale=2; $bytes/1048576" | bc) MB"
    elif ((bytes >= 1024)); then
        echo "$(echo "scale=2; $bytes/1024" | bc) KB"
    else
        echo "$bytes bytes"
    fi
}

for f in /tmp/size_test/*; do
    name=$(basename "$f")
    size=$(stat -c '%s' "$f")
    human=$(human_size $size)
    printf "%-20s %s\n" "$name" "$human"
done

echo ""
echo "=== Size Comparisons ==="

file="/tmp/size_test/large.bin"
size=$(stat -c '%s' "$file")

# Check size thresholds
if ((size > 1048576)); then
    echo "$file is larger than 1 MB"
fi

if ((size < 10485760)); then
    echo "$file is smaller than 10 MB"
fi

echo ""
echo "=== Practical: Log Rotation Check ==="

check_log_size() {
    local log=$1
    local max_mb=$2
    local max_bytes=$((max_mb * 1048576))
    
    if [[ ! -f "$log" ]]; then
        echo "$log: not found"
        return
    fi
    
    local size=$(stat -c '%s' "$log")
    local size_mb=$(echo "scale=2; $size/1048576" | bc)
    
    if ((size > max_bytes)); then
        echo "$log: ${size_mb}MB - ROTATE NEEDED (max ${max_mb}MB)"
    else
        echo "$log: ${size_mb}MB - OK (max ${max_mb}MB)"
    fi
}

check_log_size "/tmp/size_test/large.bin" 10
check_log_size "/tmp/size_test/large.bin" 2

echo ""
echo "=== Practical: Disk Space Check ==="

# Check if file can be created with size
check_space() {
    local dir=$1
    local needed_mb=$2
    
    local avail_kb=$(df -k "$dir" | awk 'NR==2 {print $4}')
    local avail_mb=$((avail_kb / 1024))
    
    if ((avail_mb >= needed_mb)); then
        echo "OK: ${avail_mb}MB available, need ${needed_mb}MB"
        return 0
    else
        echo "ERROR: Only ${avail_mb}MB available, need ${needed_mb}MB"
        return 1
    fi
}

check_space "/tmp" 100

echo ""
echo "=== Count Lines in File ==="

file="/tmp/size_test/small.txt"
lines=$(wc -l < "$file")
chars=$(wc -c < "$file")
words=$(wc -w < "$file")

echo "File: $file"
echo "  Lines: $lines"
echo "  Words: $words"
echo "  Chars: $chars"

# Cleanup
rm -rf /tmp/size_test
Output: === Empty File Test (-s) === empty.txt: empty large.bin: has content nonewline.txt: has content small.txt: has content === Get File Sizes === empty.txt 0 bytes large.bin 5242880 bytes nonewline.txt 10 bytes small.txt 12 bytes === Human-Readable Sizes === empty.txt 0 bytes large.bin 5.00 MB nonewline.txt 10 bytes small.txt 12 bytes === Size Comparisons === /tmp/size_test/large.bin is larger than 1 MB /tmp/size_test/large.bin is smaller than 10 MB === Practical: Log Rotation Check === /tmp/size_test/large.bin: 5.00MB - OK (max 10MB) /tmp/size_test/large.bin: 5.00MB - ROTATE NEEDED (max 2MB) === Practical: Disk Space Check === OK: 45678MB available, need 100MB
4
File Timestamp Operations

Working with modification, access, and change times using stat and test operators.

#!/bin/bash
# File timestamp operations

# Create test files with different times
mkdir -p /tmp/time_test
touch -d "2025-01-01 10:00:00" /tmp/time_test/january.txt
touch -d "2025-06-15 12:00:00" /tmp/time_test/june.txt
touch /tmp/time_test/today.txt  # Current time

echo "=== Get File Timestamps ==="

for f in /tmp/time_test/*; do
    name=$(basename "$f")
    
    # Human readable
    mtime=$(stat -c '%y' "$f")
    echo "$name:"
    echo "  Modified: ${mtime%.*}"
done

echo ""
echo "=== Timestamp as Epoch ==="

for f in /tmp/time_test/*; do
    name=$(basename "$f")
    epoch=$(stat -c '%Y' "$f")
    echo "$name: $epoch"
done

echo ""
echo "=== All Three Timestamps ==="

file="/tmp/time_test/today.txt"
echo "File: $file"
echo "  Access: $(stat -c '%x' "$file" | cut -d. -f1)"
echo "  Modify: $(stat -c '%y' "$file" | cut -d. -f1)"
echo "  Change: $(stat -c '%z' "$file" | cut -d. -f1)"

echo ""
echo "=== Compare File Times (-nt, -ot) ==="

file1="/tmp/time_test/january.txt"
file2="/tmp/time_test/june.txt"
file3="/tmp/time_test/today.txt"

# Newer than
if [[ "$file2" -nt "$file1" ]]; then
    echo "june.txt is newer than january.txt"
fi

if [[ "$file3" -nt "$file2" ]]; then
    echo "today.txt is newer than june.txt"
fi

# Older than
if [[ "$file1" -ot "$file2" ]]; then
    echo "january.txt is older than june.txt"
fi

echo ""
echo "=== Files Modified in Time Range ==="

# Find files modified in the last N days
find_recent() {
    local dir=$1
    local days=$2
    
    echo "Files modified in last $days days in $dir:"
    find "$dir" -maxdepth 1 -type f -mtime -$days -printf "  %f (%.19TY-%Tm-%Td %TH:%TM)\n"
}

find_recent /tmp/time_test 30

echo ""
echo "=== Practical: Check If File Is Stale ==="

is_stale() {
    local file=$1
    local max_age_secs=$2
    
    if [[ ! -f "$file" ]]; then
        echo "File not found: $file"
        return 2
    fi
    
    local now=$(date +%s)
    local mtime=$(stat -c '%Y' "$file")
    local age=$((now - mtime))
    
    if ((age > max_age_secs)); then
        local age_days=$((age / 86400))
        echo "$file is stale (${age_days} days old)"
        return 0
    else
        local age_hours=$((age / 3600))
        echo "$file is fresh (${age_hours} hours old)"
        return 1
    fi
}

is_stale /tmp/time_test/january.txt 86400   # 1 day
is_stale /tmp/time_test/today.txt 86400

echo ""
echo "=== Practical: Conditional Rebuild ==="

# Only rebuild if source is newer than target
needs_rebuild() {
    local source=$1
    local target=$2
    
    # Target doesn't exist - needs build
    if [[ ! -e "$target" ]]; then
        echo "$target doesn't exist - needs build"
        return 0
    fi
    
    # Source is newer - needs rebuild
    if [[ "$source" -nt "$target" ]]; then
        echo "$source is newer than $target - needs rebuild"
        return 0
    fi
    
    echo "$target is up to date"
    return 1
}

# Simulate source and target
touch /tmp/time_test/source.c
sleep 0.1
touch /tmp/time_test/target.o

needs_rebuild /tmp/time_test/source.c /tmp/time_test/target.o

# Modify source
touch /tmp/time_test/source.c
needs_rebuild /tmp/time_test/source.c /tmp/time_test/target.o

echo ""
echo "=== Format Timestamps ==="

file="/tmp/time_test/today.txt"
epoch=$(stat -c '%Y' "$file")

echo "Various formats for $file:"
echo "  Epoch: $epoch"
echo "  ISO:   $(date -d @$epoch '+%Y-%m-%dT%H:%M:%S')"
echo "  Human: $(date -d @$epoch '+%B %d, %Y %I:%M %p')"

# Cleanup
rm -rf /tmp/time_test
Output: === Get File Timestamps === january.txt: Modified: 2025-01-01 10:00:00 june.txt: Modified: 2025-06-15 12:00:00 today.txt: Modified: 2025-11-19 10:30:45 === Compare File Times (-nt, -ot) === june.txt is newer than january.txt today.txt is newer than june.txt january.txt is older than june.txt === Practical: Check If File Is Stale === /tmp/time_test/january.txt is stale (322 days old) /tmp/time_test/today.txt is fresh (0 hours old) === Practical: Conditional Rebuild === /tmp/time_test/target.o is up to date /tmp/time_test/source.c is newer than /tmp/time_test/target.o - needs rebuild === Format Timestamps === Various formats for /tmp/time_test/today.txt: Epoch: 1732012245 ISO: 2025-11-19T10:30:45 Human: November 19, 2025 10:30 AM
5
Using stat for Detailed Attributes

Extracting comprehensive file metadata with the stat command.

#!/bin/bash
# Detailed file attributes with stat

# Create test file
mkdir -p /tmp/stat_test
echo "Test content" > /tmp/stat_test/sample.txt
chmod 644 /tmp/stat_test/sample.txt

file="/tmp/stat_test/sample.txt"

echo "=== Default stat Output ==="
stat "$file"

echo ""
echo "=== Custom Format Output ==="

echo "File: $file"
echo "  Size:        $(stat -c '%s' "$file") bytes"
echo "  Blocks:      $(stat -c '%b' "$file")"
echo "  IO Block:    $(stat -c '%o' "$file")"
echo "  Type:        $(stat -c '%F' "$file")"
echo "  Inode:       $(stat -c '%i' "$file")"
echo "  Links:       $(stat -c '%h' "$file")"
echo "  Permissions: $(stat -c '%a' "$file") ($(stat -c '%A' "$file"))"
echo "  Owner:       $(stat -c '%U' "$file") ($(stat -c '%u' "$file"))"
echo "  Group:       $(stat -c '%G' "$file") ($(stat -c '%g' "$file"))"

echo ""
echo "=== Parse Specific Attributes ==="

# Get permissions as octal
perms=$(stat -c '%a' "$file")
echo "Octal permissions: $perms"

# Parse permission bits
owner=$((perms / 100))
group=$(((perms / 10) % 10))
other=$((perms % 10))
echo "  Owner: $owner  Group: $group  Other: $other"

# Check specific permissions from octal
if ((owner & 4)); then echo "  Owner can read"; fi
if ((owner & 2)); then echo "  Owner can write"; fi
if ((owner & 1)); then echo "  Owner can execute"; fi

echo ""
echo "=== Multiple Files ==="

touch /tmp/stat_test/file{1,2,3}.txt

echo "File listing with attributes:"
printf "%-15s %8s %10s %s\n" "Name" "Size" "Perms" "Modified"
printf "%-15s %8s %10s %s\n" "----" "----" "-----" "--------"

for f in /tmp/stat_test/*.txt; do
    name=$(basename "$f")
    size=$(stat -c '%s' "$f")
    perms=$(stat -c '%a' "$f")
    mtime=$(stat -c '%y' "$f" | cut -d. -f1)
    printf "%-15s %8d %10s %s\n" "$name" "$size" "$perms" "$mtime"
done

echo ""
echo "=== Device Information ==="

if [[ -b /dev/sda ]] || [[ -c /dev/null ]]; then
    echo "Block device /dev/sda (if exists):"
    stat -c '  Device: %t,%T  Inode: %i' /dev/sda 2>/dev/null || echo "  Not available"
    
    echo "Character device /dev/null:"
    stat -c '  Device: %t,%T  Inode: %i' /dev/null
fi

echo ""
echo "=== Practical: File Report Function ==="

file_report() {
    local file=$1
    
    if [[ ! -e "$file" ]]; then
        echo "File not found: $file"
        return 1
    fi
    
    echo "=== File Report: $file ==="
    stat -c "
Type:         %F
Size:         %s bytes
Permissions:  %A (%a)
Owner:        %U:%G
Inode:        %i
Hard Links:   %h
Modified:     %y
Accessed:     %x
Changed:      %z" "$file"
}

file_report "$file"

echo ""
echo "=== Export as JSON-like ==="

stat -c '{
  "name": "%n",
  "size": %s,
  "permissions": "%a",
  "owner": "%U",
  "group": "%G",
  "mtime": %Y
}' "$file"

# Cleanup
rm -rf /tmp/stat_test
Output: === Custom Format Output === File: /tmp/stat_test/sample.txt Size: 13 bytes Blocks: 8 IO Block: 4096 Type: regular file Inode: 1234567 Links: 1 Permissions: 644 (-rw-r--r--) Owner: user (1000) Group: user (1000) === Parse Specific Attributes === Octal permissions: 644 Owner: 6 Group: 4 Other: 4 Owner can read Owner can write === Multiple Files === File listing with attributes: Name Size Perms Modified ---- ---- ----- -------- file1.txt 0 644 2025-11-19 10:30:45 file2.txt 0 644 2025-11-19 10:30:45 file3.txt 0 644 2025-11-19 10:30:45 sample.txt 13 644 2025-11-19 10:30:45 === Export as JSON-like === { "name": "/tmp/stat_test/sample.txt", "size": 13, "permissions": "644", "owner": "user", "group": "user", "mtime": 1732012245 }
6
File Comparison Tests

Comparing files by identity, hard links, and content.

#!/bin/bash
# File comparison tests

# Create test files
mkdir -p /tmp/compare_test
echo "Content A" > /tmp/compare_test/file_a.txt
echo "Content A" > /tmp/compare_test/file_a_copy.txt
echo "Content B" > /tmp/compare_test/file_b.txt

# Create hard link
ln /tmp/compare_test/file_a.txt /tmp/compare_test/file_a_hardlink.txt

# Create symlink
ln -s /tmp/compare_test/file_a.txt /tmp/compare_test/file_a_symlink.txt

echo "=== Same File Test (-ef) ==="

# -ef tests if files are the same (same device and inode)
file1="/tmp/compare_test/file_a.txt"
file2="/tmp/compare_test/file_a_hardlink.txt"

if [[ "$file1" -ef "$file2" ]]; then
    echo "$file1 and $file2 are the same file (hard link)"
fi

# Different files with same content
file3="/tmp/compare_test/file_a_copy.txt"
if [[ "$file1" -ef "$file3" ]]; then
    echo "Same file"
else
    echo "$file1 and $file3 are different files (even with same content)"
fi

echo ""
echo "=== Inode Comparison ==="

echo "Inodes:"
for f in /tmp/compare_test/*.txt; do
    name=$(basename "$f")
    inode=$(stat -c '%i' "$f")
    printf "  %-25s inode: %s\n" "$name" "$inode"
done

echo ""
echo "=== Content Comparison ==="

# Compare by content (not bash built-in, uses cmp/diff)
compare_content() {
    local file1=$1
    local file2=$2
    
    if cmp -s "$file1" "$file2"; then
        echo "$(basename "$file1") and $(basename "$file2"): identical content"
    else
        echo "$(basename "$file1") and $(basename "$file2"): different content"
    fi
}

compare_content /tmp/compare_test/file_a.txt /tmp/compare_test/file_a_copy.txt
compare_content /tmp/compare_test/file_a.txt /tmp/compare_test/file_b.txt

echo ""
echo "=== Checksum Comparison ==="

echo "MD5 checksums:"
for f in /tmp/compare_test/*.txt; do
    if [[ -f "$f" && ! -L "$f" ]]; then
        name=$(basename "$f")
        md5=$(md5sum "$f" | cut -d' ' -f1)
        printf "  %-25s %s\n" "$name" "$md5"
    fi
done

echo ""
echo "=== Practical: Detect Duplicates ==="

find_duplicates() {
    local dir=$1
    
    declare -A checksums
    declare -a duplicates
    
    for file in "$dir"/*; do
        [[ -f "$file" && ! -L "$file" ]] || continue
        
        local md5=$(md5sum "$file" | cut -d' ' -f1)
        
        if [[ -n "${checksums[$md5]}" ]]; then
            echo "Duplicate: $(basename "$file") = $(basename "${checksums[$md5]}")"
        else
            checksums[$md5]="$file"
        fi
    done
}

find_duplicates /tmp/compare_test

echo ""
echo "=== Hard Link Detection ==="

find_hardlinks() {
    local file=$1
    local dir=$2
    
    local inode=$(stat -c '%i' "$file")
    
    echo "Hard links to $(basename "$file") (inode $inode):"
    find "$dir" -inum "$inode" -printf "  %p\n"
}

find_hardlinks /tmp/compare_test/file_a.txt /tmp/compare_test

echo ""
echo "=== Symlink Resolution ==="

link="/tmp/compare_test/file_a_symlink.txt"
echo "Symlink: $link"
echo "  Points to: $(readlink "$link")"
echo "  Resolved:  $(readlink -f "$link")"

# Check if symlink target exists
if [[ -e "$link" ]]; then
    echo "  Target exists"
else
    echo "  Broken link"
fi

# Cleanup
rm -rf /tmp/compare_test
Output: === Same File Test (-ef) === /tmp/compare_test/file_a.txt and /tmp/compare_test/file_a_hardlink.txt are the same file (hard link) /tmp/compare_test/file_a.txt and /tmp/compare_test/file_a_copy.txt are different files (even with same content) === Inode Comparison === Inodes: file_a.txt inode: 1234567 file_a_copy.txt inode: 1234568 file_a_hardlink.txt inode: 1234567 file_b.txt inode: 1234569 === Content Comparison === file_a.txt and file_a_copy.txt: identical content file_a.txt and file_b.txt: different content === Checksum Comparison === MD5 checksums: file_a.txt d8e8fca2dc0f896fd7cb4cb0031ba249 file_a_copy.txt d8e8fca2dc0f896fd7cb4cb0031ba249 file_a_hardlink.txt d8e8fca2dc0f896fd7cb4cb0031ba249 file_b.txt a9f5f2e3c8d87cb04e7c456e29d3fc59 === Practical: Detect Duplicates === Duplicate: file_a_hardlink.txt = file_a.txt Duplicate: file_a_copy.txt = file_a.txt === Hard Link Detection === Hard links to file_a.txt (inode 1234567): /tmp/compare_test/file_a.txt /tmp/compare_test/file_a_hardlink.txt
7
Terminal and Descriptor Tests

Testing file descriptors and terminal connections for interactive scripts.

#!/bin/bash
# Terminal and file descriptor tests

echo "=== Terminal Tests (-t) ==="

# Test if file descriptor is a terminal
if [[ -t 0 ]]; then
    echo "stdin (fd 0) is a terminal"
else
    echo "stdin is NOT a terminal (piped or redirected)"
fi

if [[ -t 1 ]]; then
    echo "stdout (fd 1) is a terminal"
else
    echo "stdout is NOT a terminal (piped or redirected)"
fi

if [[ -t 2 ]]; then
    echo "stderr (fd 2) is a terminal"
else
    echo "stderr is NOT a terminal"
fi

echo ""
echo "=== Practical: Interactive Detection ==="

is_interactive() {
    # Check if both stdin and stdout are terminals
    [[ -t 0 && -t 1 ]]
}

if is_interactive; then
    echo "Running interactively"
else
    echo "Running non-interactively (script/pipe)"
fi

echo ""
echo "=== Practical: Colorized Output ==="

# Only use colors if outputting to terminal
if [[ -t 1 ]]; then
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    NC='\033[0m'
    
    echo -e "${GREEN}Success message${NC}"
    echo -e "${RED}Error message${NC}"
else
    # No colors for pipes/files
    echo "Success message"
    echo "Error message"
fi

echo ""
echo "=== Practical: Progress Display ==="

show_progress() {
    local current=$1
    local total=$2
    
    if [[ -t 1 ]]; then
        # Terminal: update in place
        printf "\rProgress: %d/%d (%d%%)" "$current" "$total" "$((current * 100 / total))"
    else
        # Not terminal: new line each time (or suppress)
        echo "Progress: $current/$total"
    fi
}

# Demo (would normally be in a loop)
echo "Progress display:"
show_progress 50 100
echo ""

echo ""
echo "=== Practical: Prompt Only If Interactive ==="

get_confirmation() {
    local message=$1
    
    if [[ -t 0 ]]; then
        # Can read from user
        read -p "$message (y/n) " response
        [[ "$response" =~ ^[Yy] ]]
    else
        # Non-interactive: assume yes or default
        echo "$message - assuming yes (non-interactive)"
        return 0
    fi
}

# get_confirmation "Continue?"  # Would wait for input

echo ""
echo "=== Check Specific File Descriptors ==="

# Test if fd 3 is open
if [[ -e /dev/fd/3 ]]; then
    echo "File descriptor 3 is open"
else
    echo "File descriptor 3 is not open"
fi

# Open fd 3 and test
exec 3>/tmp/fd3_test.txt
if [[ -e /dev/fd/3 ]]; then
    echo "File descriptor 3 is now open"
fi
exec 3>&-  # Close fd 3
rm -f /tmp/fd3_test.txt

echo ""
echo "=== Practical: Logging with Redirection ==="

log_message() {
    local level=$1
    local message=$2
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    # Always log to stderr (or log file)
    echo "[$timestamp] [$level] $message" >&2
    
    # Also show on terminal if interactive
    if [[ -t 1 ]]; then
        case "$level" in
            ERROR)   echo -e "\033[31m$message\033[0m" ;;
            WARNING) echo -e "\033[33m$message\033[0m" ;;
            INFO)    echo "$message" ;;
        esac
    fi
}

log_message "INFO" "Application started"
log_message "WARNING" "Configuration not found, using defaults"
Output: === Terminal Tests (-t) === stdin (fd 0) is a terminal stdout (fd 1) is a terminal stderr (fd 2) is a terminal === Practical: Interactive Detection === Running interactively === Practical: Colorized Output === Success message (in green) Error message (in red) === Practical: Progress Display === Progress display: Progress: 50/100 (50%) === Check Specific File Descriptors === File descriptor 3 is not open File descriptor 3 is now open === Practical: Logging with Redirection === [2025-11-19 10:30:45] [INFO] Application started Application started [2025-11-19 10:30:45] [WARNING] Configuration not found, using defaults Configuration not found, using defaults
πŸ’‘ When to Use Terminal Tests
Use [[ -t 1 ]] to detect if output is going to a terminal, so you can enable colors, progress bars, and interactive features only when appropriate.
8
Directory Operations

Testing directories, checking contents, and traversing paths.

#!/bin/bash
# Directory operations

# Create test structure
mkdir -p /tmp/dir_test/subdir1/nested
mkdir -p /tmp/dir_test/subdir2
mkdir -p /tmp/dir_test/empty_dir
touch /tmp/dir_test/file.txt
touch /tmp/dir_test/subdir1/file1.txt
touch /tmp/dir_test/subdir1/nested/file2.txt

echo "=== Directory Tests ==="

dir="/tmp/dir_test"

if [[ -d "$dir" ]]; then
    echo "$dir is a directory"
fi

if [[ -d "$dir/file.txt" ]]; then
    echo "file.txt is a directory"
else
    echo "file.txt is not a directory"
fi

echo ""
echo "=== Check If Directory Is Empty ==="

is_empty_dir() {
    local dir=$1
    
    if [[ ! -d "$dir" ]]; then
        echo "$dir is not a directory"
        return 2
    fi
    
    if [[ -z "$(ls -A "$dir")" ]]; then
        echo "$dir is empty"
        return 0
    else
        echo "$dir is not empty"
        return 1
    fi
}

is_empty_dir /tmp/dir_test/empty_dir
is_empty_dir /tmp/dir_test/subdir1

echo ""
echo "=== Count Directory Contents ==="

count_contents() {
    local dir=$1
    
    local files=$(find "$dir" -maxdepth 1 -type f | wc -l)
    local dirs=$(find "$dir" -maxdepth 1 -type d | wc -l)
    ((dirs--))  # Subtract the directory itself
    
    echo "$dir contains: $files files, $dirs subdirectories"
}

count_contents /tmp/dir_test
count_contents /tmp/dir_test/subdir1

echo ""
echo "=== Directory Permissions ==="

# Check if can enter directory
dir="/tmp/dir_test"
if [[ -x "$dir" ]]; then
    echo "Can enter $dir (execute permission)"
fi

# Check if can list directory
if [[ -r "$dir" ]]; then
    echo "Can list $dir (read permission)"
fi

# Check if can create files in directory
if [[ -w "$dir" ]]; then
    echo "Can create files in $dir (write permission)"
fi

echo ""
echo "=== Safe Directory Creation ==="

safe_mkdir() {
    local dir=$1
    
    if [[ -e "$dir" ]]; then
        if [[ -d "$dir" ]]; then
            echo "Directory already exists: $dir"
            return 0
        else
            echo "ERROR: Path exists but is not a directory: $dir"
            return 1
        fi
    fi
    
    local parent=$(dirname "$dir")
    if [[ ! -d "$parent" ]]; then
        echo "ERROR: Parent directory doesn't exist: $parent"
        return 1
    fi
    
    if [[ ! -w "$parent" ]]; then
        echo "ERROR: Cannot write to parent: $parent"
        return 1
    fi
    
    mkdir "$dir"
    echo "Created directory: $dir"
    return 0
}

safe_mkdir /tmp/dir_test/newdir
safe_mkdir /tmp/dir_test/file.txt  # Should fail

echo ""
echo "=== Traverse Directory Tree ==="

traverse() {
    local dir=$1
    local indent=${2:-0}
    local prefix=""
    
    for ((i=0; i<indent; i++)); do
        prefix+="  "
    done
    
    for item in "$dir"/*; do
        [[ -e "$item" ]] || continue
        
        local name=$(basename "$item")
        
        if [[ -d "$item" ]]; then
            echo "${prefix}πŸ“ $name/"
            traverse "$item" $((indent + 1))
        else
            echo "${prefix}πŸ“„ $name"
        fi
    done
}

echo "Directory tree:"
traverse /tmp/dir_test

echo ""
echo "=== Practical: Ensure Directory Structure ==="

ensure_structure() {
    local base=$1
    shift
    
    for dir in "$@"; do
        local path="$base/$dir"
        if [[ ! -d "$path" ]]; then
            mkdir -p "$path"
            echo "Created: $path"
        fi
    done
}

ensure_structure /tmp/dir_test "logs" "cache" "config"

# Cleanup
rm -rf /tmp/dir_test
Output: === Directory Tests === /tmp/dir_test is a directory file.txt is not a directory === Check If Directory Is Empty === /tmp/dir_test/empty_dir is empty /tmp/dir_test/subdir1 is not empty === Count Directory Contents === /tmp/dir_test contains: 1 files, 3 subdirectories /tmp/dir_test/subdir1 contains: 1 files, 1 subdirectories === Directory Permissions === Can enter /tmp/dir_test (execute permission) Can list /tmp/dir_test (read permission) Can create files in /tmp/dir_test (write permission) === Safe Directory Creation === Created directory: /tmp/dir_test/newdir ERROR: Path exists but is not a directory: /tmp/dir_test/file.txt === Traverse Directory Tree === Directory tree: πŸ“„ file.txt πŸ“ empty_dir/ πŸ“ subdir1/ πŸ“„ file1.txt πŸ“ nested/ πŸ“„ file2.txt πŸ“ subdir2/ === Practical: Ensure Directory Structure === Created: /tmp/dir_test/logs Created: /tmp/dir_test/cache Created: /tmp/dir_test/config
9
Combining Tests with Logic

Complex file testing with AND, OR, NOT, and nested conditions.

#!/bin/bash
# Combining tests with logic

# Create test files
mkdir -p /tmp/logic_test
echo "content" > /tmp/logic_test/readable.txt
chmod 644 /tmp/logic_test/readable.txt

touch /tmp/logic_test/empty.txt
chmod 444 /tmp/logic_test/empty.txt

touch /tmp/logic_test/executable.sh
chmod 755 /tmp/logic_test/executable.sh

echo "=== Combining Tests with && and || ==="

file="/tmp/logic_test/readable.txt"

# AND: both must be true
if [[ -f "$file" && -r "$file" ]]; then
    echo "$file is a readable file"
fi

# OR: at least one true
if [[ -f "$file" || -d "$file" ]]; then
    echo "$file exists (file or directory)"
fi

# NOT
if [[ ! -d "$file" ]]; then
    echo "$file is not a directory"
fi

echo ""
echo "=== Complex Conditions ==="

check_file() {
    local f=$1
    local name=$(basename "$f")
    
    # File must exist, be readable, not empty, and not a directory
    if [[ -e "$f" && -r "$f" && -s "$f" && ! -d "$f" ]]; then
        echo "$name: valid data file"
    else
        echo -n "$name: invalid ("
        [[ ! -e "$f" ]] && echo -n "missing, "
        [[ -e "$f" && ! -r "$f" ]] && echo -n "not readable, "
        [[ -e "$f" && ! -s "$f" ]] && echo -n "empty, "
        [[ -d "$f" ]] && echo -n "is directory, "
        echo ")"
    fi
}

check_file /tmp/logic_test/readable.txt
check_file /tmp/logic_test/empty.txt
check_file /tmp/logic_test/nonexistent.txt

echo ""
echo "=== Nested Conditions ==="

analyze_file() {
    local f=$1
    
    if [[ ! -e "$f" ]]; then
        echo "File does not exist: $f"
        return 1
    fi
    
    if [[ -d "$f" ]]; then
        echo "Is a directory"
        if [[ -r "$f" && -x "$f" ]]; then
            echo "  Can list contents"
        fi
    elif [[ -f "$f" ]]; then
        echo "Is a regular file"
        
        if [[ -s "$f" ]]; then
            echo "  Has content"
        else
            echo "  Is empty"
        fi
        
        if [[ -r "$f" ]]; then
            if [[ -w "$f" ]]; then
                echo "  Readable and writable"
            else
                echo "  Readable only"
            fi
        else
            echo "  Not readable"
        fi
        
        if [[ -x "$f" ]]; then
            echo "  Is executable"
        fi
    elif [[ -L "$f" ]]; then
        echo "Is a symbolic link"
    else
        echo "Is a special file"
    fi
}

echo "Analyzing files:"
echo ""
echo "/tmp/logic_test/readable.txt:"
analyze_file /tmp/logic_test/readable.txt

echo ""
echo "/tmp/logic_test/executable.sh:"
analyze_file /tmp/logic_test/executable.sh

echo ""
echo "=== Practical: Validate Input File ==="

validate_input() {
    local file=$1
    local required_perms=$2  # r, w, rw, rx, etc.
    
    # Must exist
    if [[ ! -e "$file" ]]; then
        echo "ERROR: File not found: $file"
        return 1
    fi
    
    # Must be regular file
    if [[ ! -f "$file" ]]; then
        echo "ERROR: Not a regular file: $file"
        return 1
    fi
    
    # Check required permissions
    local missing=""
    if [[ "$required_perms" == *r* && ! -r "$file" ]]; then
        missing+="read "
    fi
    if [[ "$required_perms" == *w* && ! -w "$file" ]]; then
        missing+="write "
    fi
    if [[ "$required_perms" == *x* && ! -x "$file" ]]; then
        missing+="execute "
    fi
    
    if [[ -n "$missing" ]]; then
        echo "ERROR: Missing permissions ($missing): $file"
        return 1
    fi
    
    # Must have content for input files
    if [[ "$required_perms" == *r* && ! -s "$file" ]]; then
        echo "WARNING: File is empty: $file"
    fi
    
    echo "OK: $file (permissions: $required_perms)"
    return 0
}

validate_input /tmp/logic_test/readable.txt "r"
validate_input /tmp/logic_test/empty.txt "rw"
validate_input /tmp/logic_test/executable.sh "rx"

# Cleanup
rm -rf /tmp/logic_test
Output: === Combining Tests with && and || === /tmp/logic_test/readable.txt is a readable file /tmp/logic_test/readable.txt exists (file or directory) /tmp/logic_test/readable.txt is not a directory === Complex Conditions === readable.txt: valid data file empty.txt: invalid (empty, ) nonexistent.txt: invalid (missing, ) === Nested Conditions === Analyzing files: /tmp/logic_test/readable.txt: Is a regular file Has content Readable and writable /tmp/logic_test/executable.sh: Is a regular file Is empty Readable and writable Is executable === Practical: Validate Input File === OK: /tmp/logic_test/readable.txt (permissions: r) ERROR: Missing permissions (write ): /tmp/logic_test/empty.txt OK: /tmp/logic_test/executable.sh (permissions: rx)
10
Complete File Testing Library

A reusable library of file testing utility functions.

#!/bin/bash
# Complete file testing library

#######################################
# Existence and Type
#######################################

file_exists() {
    [[ -e "$1" ]]
}

is_file() {
    [[ -f "$1" ]]
}

is_dir() {
    [[ -d "$1" ]]
}

is_link() {
    [[ -L "$1" ]]
}

is_empty_file() {
    [[ -f "$1" && ! -s "$1" ]]
}

is_empty_dir() {
    [[ -d "$1" ]] && [[ -z "$(ls -A "$1")" ]]
}

#######################################
# Permissions
#######################################

is_readable() {
    [[ -r "$1" ]]
}

is_writable() {
    [[ -w "$1" ]]
}

is_executable() {
    [[ -x "$1" ]]
}

is_owned() {
    [[ -O "$1" ]]
}

#######################################
# Comparisons
#######################################

is_newer() {
    [[ "$1" -nt "$2" ]]
}

is_older() {
    [[ "$1" -ot "$2" ]]
}

is_same_file() {
    [[ "$1" -ef "$2" ]]
}

#######################################
# Attributes
#######################################

get_size() {
    stat -c '%s' "$1" 2>/dev/null
}

get_perms() {
    stat -c '%a' "$1" 2>/dev/null
}

get_owner() {
    stat -c '%U' "$1" 2>/dev/null
}

get_group() {
    stat -c '%G' "$1" 2>/dev/null
}

get_mtime() {
    stat -c '%Y' "$1" 2>/dev/null
}

get_type() {
    stat -c '%F' "$1" 2>/dev/null
}

#######################################
# Utilities
#######################################

# Human-readable size
human_size() {
    local bytes=$(get_size "$1")
    [[ -z "$bytes" ]] && return 1
    
    if ((bytes >= 1073741824)); then
        echo "$(echo "scale=1; $bytes/1073741824" | bc)G"
    elif ((bytes >= 1048576)); then
        echo "$(echo "scale=1; $bytes/1048576" | bc)M"
    elif ((bytes >= 1024)); then
        echo "$(echo "scale=1; $bytes/1024" | bc)K"
    else
        echo "${bytes}B"
    fi
}

# File age in seconds
file_age() {
    local mtime=$(get_mtime "$1")
    [[ -z "$mtime" ]] && return 1
    echo $(($(date +%s) - mtime))
}

# Describe file
describe_file() {
    local f=$1
    
    if [[ ! -e "$f" ]]; then
        echo "Not found"
        return 1
    fi
    
    local type=$(get_type "$f")
    local size=$(human_size "$f")
    local perms=$(get_perms "$f")
    local owner=$(get_owner "$f")
    
    echo "$type, $size, mode $perms, owner $owner"
}

# Validate file for operation
validate_file() {
    local f=$1
    local mode=$2  # r=read, w=write, x=exec, d=dir, f=file
    
    [[ ! -e "$f" ]] && { echo "not found"; return 1; }
    
    [[ "$mode" == *f* && ! -f "$f" ]] && { echo "not a file"; return 1; }
    [[ "$mode" == *d* && ! -d "$f" ]] && { echo "not a directory"; return 1; }
    [[ "$mode" == *r* && ! -r "$f" ]] && { echo "not readable"; return 1; }
    [[ "$mode" == *w* && ! -w "$f" ]] && { echo "not writable"; return 1; }
    [[ "$mode" == *x* && ! -x "$f" ]] && { echo "not executable"; return 1; }
    
    echo "ok"
    return 0
}

#######################################
# Demo
#######################################

echo "========================================"
echo "    File Testing Library Demo"
echo "========================================"

# Create test files
mkdir -p /tmp/lib_test
echo "Hello World" > /tmp/lib_test/sample.txt
chmod 644 /tmp/lib_test/sample.txt
touch /tmp/lib_test/empty.txt
mkdir /tmp/lib_test/subdir

echo ""
echo "=== Type Tests ==="
echo "sample.txt:"
is_file /tmp/lib_test/sample.txt && echo "  is_file: yes"
is_dir /tmp/lib_test/sample.txt || echo "  is_dir: no"

echo "subdir:"
is_dir /tmp/lib_test/subdir && echo "  is_dir: yes"

echo ""
echo "=== Empty Tests ==="
is_empty_file /tmp/lib_test/empty.txt && echo "empty.txt is empty"
is_empty_file /tmp/lib_test/sample.txt || echo "sample.txt is not empty"
is_empty_dir /tmp/lib_test/subdir && echo "subdir is empty"

echo ""
echo "=== Permissions ==="
echo "sample.txt permissions:"
is_readable /tmp/lib_test/sample.txt && echo "  readable"
is_writable /tmp/lib_test/sample.txt && echo "  writable"
is_executable /tmp/lib_test/sample.txt || echo "  not executable"

echo ""
echo "=== Attributes ==="
echo "sample.txt:"
echo "  Size: $(get_size /tmp/lib_test/sample.txt) bytes"
echo "  Human: $(human_size /tmp/lib_test/sample.txt)"
echo "  Perms: $(get_perms /tmp/lib_test/sample.txt)"
echo "  Owner: $(get_owner /tmp/lib_test/sample.txt)"
echo "  Age: $(file_age /tmp/lib_test/sample.txt) seconds"

echo ""
echo "=== Describe ==="
echo "sample.txt: $(describe_file /tmp/lib_test/sample.txt)"
echo "subdir: $(describe_file /tmp/lib_test/subdir)"

echo ""
echo "=== Validate ==="
echo "sample.txt (rf): $(validate_file /tmp/lib_test/sample.txt rf)"
echo "sample.txt (rx): $(validate_file /tmp/lib_test/sample.txt rx)"
echo "subdir (dr): $(validate_file /tmp/lib_test/subdir dr)"

# Cleanup
rm -rf /tmp/lib_test

echo ""
echo "========================================"
Output: ======================================== File Testing Library Demo ======================================== === Type Tests === sample.txt: is_file: yes is_dir: no subdir: is_dir: yes === Empty Tests === empty.txt is empty sample.txt is not empty subdir is empty === Permissions === sample.txt permissions: readable writable not executable === Attributes === sample.txt: Size: 12 bytes Human: 12B Perms: 644 Owner: user Age: 0 seconds === Describe === sample.txt: regular file, 12B, mode 644, owner user subdir: directory, 4.0K, mode 755, owner user === Validate === sample.txt (rf): ok sample.txt (rx): not executable subdir (dr): ok ========================================

Quick Reference

Essential File Tests

# Existence
[[ -e file ]]    # Exists (any type)
[[ -f file ]]    # Regular file
[[ -d file ]]    # Directory
[[ -L file ]]    # Symbolic link

# Permissions
[[ -r file ]]    # Readable
[[ -w file ]]    # Writable
[[ -x file ]]    # Executable

# Size
[[ -s file ]]    # Size > 0

# Comparison
[[ f1 -nt f2 ]]  # f1 newer than f2
[[ f1 -ot f2 ]]  # f1 older than f2
[[ f1 -ef f2 ]]  # Same file (hard link)

# Terminal
[[ -t 0 ]]       # stdin is terminal
[[ -t 1 ]]       # stdout is terminal

Common stat Formats

stat -c '%s' file    # Size in bytes
stat -c '%a' file    # Octal permissions (644)
stat -c '%A' file    # Human permissions (-rw-r--r--)
stat -c '%U:%G' file # Owner:Group
stat -c '%Y' file    # Mtime as epoch
stat -c '%y' file    # Mtime human readable

Common Patterns

TaskCode
Check before read[[ -f "$f" && -r "$f" ]]
Check before write[[ -f "$f" && -w "$f" ]]
Safe create[[ ! -e "$f" ]] && touch "$f"
Get file sizestat -c '%s' "$f"
Check age$(($(date +%s) - $(stat -c '%Y' "$f")))
Empty dir check[[ -z "$(ls -A "$dir")" ]]
βœ“ Best Practices
βœ— Common Mistakes
← Back to BashSpecial Index ↑ Back to EXPANDED