6.3. String & File Tests#
Beyond numbers, bash allows you to test strings (is it empty? does it contain something?) and test files (does it exist? is it readable? is it a directory?). These tests are essential for writing robust scripts.
6.3.1. Common Pitfalls#
6.3.1.1. ❌ Testing Variables Without Quotes#
# Wrong: Can cause "too many arguments" error
$ var="hello world"
$ [ -n $var ] && echo "ok" # Error! -n gets "hello" only
# Correct: Always quote
$ [ -n "$var" ] && echo "ok"
ok
6.3.1.2. ❌ Forgetting to Check File Existence#
# Wrong: Can give confusing errors
$ [ -r "$file" ] || echo "can't read" # Works but doesn't say why
# Better: Check existence first
$ if [ ! -e "$file" ]; then
> echo "File doesn't exist"
> elif [ ! -r "$file" ]; then
> echo "File not readable"
> fi
6.3.1.3. ❌ Using -e for All Checks#
# Wrong: -e just checks existence
$ [ -e "$var" ] && echo "var is a file" # But var is not a filename!
# Correct: Use appropriate test for the context
$ [ -f "$var" ] && echo "var is a regular file"
$ [ -n "$var" ] && echo "var has string content"
6.3.2. Real-World Examples#
6.3.2.1. Example 1: Safe Script Execution#
#!/bin/bash
# Check if config file exists and is readable
if [ ! -r "/etc/myapp/config" ]; then
echo "Error: Config file not found or not readable"
exit 1
fi
# Check if a directory exists
if [ ! -d "/var/log/myapp" ]; then
mkdir -p "/var/log/myapp"
fi
echo "All checks passed"
6.3.2.2. Example 2: Process Backup Script#
#!/bin/bash
SOURCE="$1"
TARGET="$2"
# Validate inputs
if [ -z "$SOURCE" ] || [ -z "$TARGET" ]; then
echo "Usage: $0 <source> <target>"
exit 1
fi
# Check source exists
if [ ! -e "$SOURCE" ]; then
echo "Error: Source '$SOURCE' does not exist"
exit 1
fi
# Backup file or directory
if [ -f "$SOURCE" ]; then
cp "$SOURCE" "$TARGET"
echo "File backed up"
elif [ -d "$SOURCE" ]; then
cp -r "$SOURCE" "$TARGET"
echo "Directory backed up"
else
echo "Unknown file type"
exit 1
fi
6.3.2.3. Example 3: Log Rotation Check#
#!/bin/bash
LOGFILE="/var/log/myapp.log"
# Only rotate if file exists and is non-empty
if [ -s "$LOGFILE" ]; then
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mv "$LOGFILE" "$LOGFILE.$TIMESTAMP"
gzip "$LOGFILE.$TIMESTAMP"
touch "$LOGFILE"
echo "Log rotated"
fi
6.3.2.4. File Size and Age Tests#
# -s: file EXISTS and has NON-ZERO size
$ [ -s /etc/passwd ] && echo "file has content"
file has content
# Empty file test (negation)
$ [ ! -s "$file" ] && echo "file is empty or doesn't exist"
# -z: zero length (for strings, but also useful logic)
# These are typically for files:
# Newer than: bash only (not POSIX)
# Older than: bash only (not POSIX)
Size tests:
Test |
Meaning |
Example |
|---|---|---|
|
File > 0 bytes |
|
|
File is empty |
|
6.3.2.5. Permission and Accessibility Tests#
# -r: file EXISTS and is READABLE
$ [ -r /etc/passwd ] && echo "you can read this"
you can read this
# -w: file EXISTS and is WRITABLE
$ [ -w /tmp ] && echo "you can write here"
you can write here
# -x: file EXISTS and is eXECUTABLE
$ [ -x /bin/bash ] && echo "you can execute this"
you can execute this
# -u: file has SETUID bit set (advanced)
# -g: file has SETGID bit set (advanced)
# -k: file has STICKY bit set (advanced)
# Real-world example: safely delete a file only if writable
$ if [ -w "$file" ]; then
> rm "$file"
> fi
Permission tests:
Test |
Meaning |
Example |
|---|---|---|
|
Readable |
|
|
Writable |
|
|
Executable |
|
|
Has SETUID |
|
|
Has SETGID |
|
|
Has STICKY |
|
6.3.2.6. Existence and Type Tests#
# -e: file EXISTS (any type)
$ [ -e /etc/passwd ] && echo "file exists"
file exists
# -f: file EXISTS and is a regular FILE
$ [ -f /etc/passwd ] && echo "is a file"
is a file
# -d: file EXISTS and is a DIRECTORY
$ [ -d /etc ] && echo "is a directory"
is a directory
# -L: file EXISTS and is a SYMLINK (symbolic link)
$ [ -L /etc/rc ] && echo "is a symlink"
# May or may not print (depends on system)
# -S: file EXISTS and is a SOCKET
# -p: file EXISTS and is a named PIPE
# -b: file EXISTS and is a BLOCK device
# -c: file EXISTS and is a CHARACTER device
File type tests:
Test |
Meaning |
Example |
|---|---|---|
|
File exists |
|
|
Is regular file |
|
|
Is directory |
|
|
Is symlink |
|
|
Is socket |
|
|
Is named pipe |
|
|
Is block device |
|
|
Is char device |
|
6.3.3. File Tests#
Check file properties before using them:
6.3.3.1. Test if String is NOT Empty: -n#
# -n: true if string is NOT empty
$ name="John"
$ [ -n "$name" ] && echo "name has content"
name has content
# Can omit -n (default behavior)
$ [ "$name" ] && echo "has content" # Same as [ -n "$name" ]
has content
# But explicit -n is clearer
$ [ -n "$name" ] && echo "definitely has content"
definitely has content
6.3.3.2. Test if String is Empty: -z#
# -z: true if string is EMPTY (zero length)
$ name=""
$ [ -z "$name" ] && echo "name is empty"
name is empty
# Common use: check if variable was passed
$ [ -z "$1" ] && echo "no argument provided"
# With actual content
$ name="John"
$ [ -z "$name" ] && echo "empty" || echo "not empty"
not empty
6.3.4. String Tests#
Test properties of strings (empty, set, etc.):