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

-s

File > 0 bytes

[ -s /etc/hosts ]

! -s

File is empty

[ ! -s $file ]

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

-r

Readable

[ -r /etc/passwd ]

-w

Writable

[ -w /tmp ]

-x

Executable

[ -x /bin/bash ]

-u

Has SETUID

[ -u /bin/sudo ]

-g

Has SETGID

[ -g /bin/ls ]

-k

Has STICKY

[ -k /tmp ]

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

-e

File exists

[ -e /tmp/file ]

-f

Is regular file

[ -f /etc/passwd ]

-d

Is directory

[ -d /home ]

-L

Is symlink

[ -L /etc/rc ]

-S

Is socket

[ -S /tmp/socket ]

-p

Is named pipe

[ -p /tmp/fifo ]

-b

Is block device

[ -b /dev/sda ]

-c

Is char device

[ -c /dev/null ]

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.):