Input Validation
Never trust user input! Validating input is crucial for writing robust, secure scripts that handle edge cases gracefully.
Why Validate Input?
Unvalidated input can cause:
- Script crashes
- Security vulnerabilities
- Unexpected behavior
- Data corruption
# Dangerous without validation
read -p "Enter filename: " FILE
rm "$FILE" # What if FILE is "/" or "* "?
Basic Validation Pattern
The standard pattern is a loop that continues until valid input is received:
#!/bin/bash
while true; do
read -rp "Enter a number: " NUM
if [[ "$NUM" =~ ^[0-9]+$ ]]; then
break
fi
echo "Error: Please enter a valid number"
done
echo "You entered: $NUM"
Checking for Empty Input
Always check if required input was provided:
#!/bin/bash
read -rp "Enter your name: " NAME
# Method 1: -z checks if string is empty
if [ -z "$NAME" ]; then
echo "Error: Name cannot be empty"
exit 1
fi
# Method 2: -n checks if string is non-empty
if [ -n "$NAME" ]; then
echo "Hello, $NAME!"
fi
Exercise: Validate Non-Empty
Check for empty input:
Validating Numbers
Different patterns for different number types:
#!/bin/bash
# Positive integers only
if [[ "$INPUT" =~ ^[0-9]+$ ]]; then
echo "Valid positive integer"
fi
# Integers (including negative)
if [[ "$INPUT" =~ ^-?[0-9]+$ ]]; then
echo "Valid integer"
fi
# Decimal numbers
if [[ "$INPUT" =~ ^-?[0-9]*\.?[0-9]+$ ]]; then
echo "Valid decimal"
fi
# Range check
if [ "$NUM" -ge 1 ] && [ "$NUM" -le 100 ]; then
echo "Number is between 1 and 100"
fi
Validating Strings
Common string validation patterns:
#!/bin/bash
# Alphabetic only
if [[ "$INPUT" =~ ^[a-zA-Z]+$ ]]; then
echo "Valid alphabetic string"
fi
# Alphanumeric
if [[ "$INPUT" =~ ^[a-zA-Z0-9]+$ ]]; then
echo "Valid alphanumeric"
fi
# No spaces
if [[ ! "$INPUT" =~ [[:space:]] ]]; then
echo "No spaces in input"
fi
# Specific length
if [ ${#INPUT} -ge 3 ] && [ ${#INPUT} -le 20 ]; then
echo "Length is between 3 and 20"
fi
Validating Email Format
A simple email validation:
#!/bin/bash
EMAIL="$1"
if [[ "$EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Valid email format"
else
echo "Invalid email format"
fi
Validating File Paths
Check files before operations:
#!/bin/bash
FILE="$1"
# Check if file exists
if [ ! -e "$FILE" ]; then
echo "Error: File does not exist"
exit 1
fi
# Check if it's a regular file
if [ ! -f "$FILE" ]; then
echo "Error: Not a regular file"
exit 1
fi
# Check if readable
if [ ! -r "$FILE" ]; then
echo "Error: File not readable"
exit 1
fi
# Check if writable
if [ ! -w "$FILE" ]; then
echo "Error: File not writable"
exit 1
fi
echo "File is valid and accessible"
Exercise: Validate File Exists
Check if a path exists:
Yes/No Validation
A reusable confirmation function:
#!/bin/bash
confirm() {
local prompt="${1:-Are you sure?}"
while true; do
read -rp "$prompt [y/n]: " REPLY
case "$REPLY" in
[Yy]|[Yy][Ee][Ss]) return 0 ;;
[Nn]|[Nn][Oo]) return 1 ;;
*) echo "Please answer yes or no" ;;
esac
done
}
if confirm "Delete all files?"; then
echo "Deleting..."
else
echo "Cancelled"
fi
Validating Command Line Arguments
Check arguments at the start of your script:
#!/bin/bash
# Check argument count
if [ $# -lt 2 ]; then
echo "Usage: $0 <source> <destination>"
exit 1
fi
SOURCE="$1"
DEST="$2"
# Validate source exists
if [ ! -e "$SOURCE" ]; then
echo "Error: Source '$SOURCE' does not exist"
exit 1
fi
# Validate destination directory exists
if [ ! -d "$(dirname "$DEST")" ]; then
echo "Error: Destination directory does not exist"
exit 1
fi
# Proceed with validated input
echo "Copying $SOURCE to $DEST..."
Safe Path Handling
Prevent path traversal attacks:
#!/bin/bash
# Don't allow paths outside intended directory
SAFE_DIR="/home/user/uploads"
FILE="$1"
# Get the real path (resolves symlinks, .., etc.)
REAL_PATH=$(realpath -m "$SAFE_DIR/$FILE")
# Check if it's still within safe directory
if [[ "$REAL_PATH" != "$SAFE_DIR"/* ]]; then
echo "Error: Path outside allowed directory"
exit 1
fi
Input Sanitization
Remove dangerous characters:
#!/bin/bash
# Sanitize filename input
sanitize_filename() {
local input="$1"
# Remove everything except alphanumeric, dash, underscore, dot
echo "$input" | tr -cd '[:alnum:]._-'
}
read -rp "Enter filename: " RAW_NAME
SAFE_NAME=$(sanitize_filename "$RAW_NAME")
echo "Sanitized: $SAFE_NAME"
Creating a Validation Library
Build reusable validation functions:
#!/bin/bash
# validation_lib.sh
is_integer() {
[[ "$1" =~ ^-?[0-9]+$ ]]
}
is_positive_integer() {
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -gt 0 ]
}
is_in_range() {
local num="$1" min="$2" max="$3"
is_integer "$num" && [ "$num" -ge "$min" ] && [ "$num" -le "$max" ]
}
is_email() {
[[ "$1" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
}
is_file() {
[ -f "$1" ]
}
is_directory() {
[ -d "$1" ]
}
# Usage:
# source validation_lib.sh
# if is_integer "$input"; then ...
Exercise: Number Range Validation
Validate a number is in range:
Best Practices
- Validate early - Check input before using it
- Fail safely - Exit with clear error messages
- Be specific - Tell users exactly what's wrong
- Use functions - Create reusable validators
- Defense in depth - Validate at multiple points
- Sanitize when needed - Remove dangerous characters
#!/bin/bash
# Complete validation example
die() {
echo "Error: $1" >&2
exit 1
}
validate_args() {
[ $# -ge 1 ] || die "Missing required argument"
[ -n "$1" ] || die "Argument cannot be empty"
}
validate_file() {
[ -f "$1" ] || die "File not found: $1"
[ -r "$1" ] || die "File not readable: $1"
}
# Main script
validate_args "$@"
FILE="$1"
validate_file "$FILE"
echo "Processing: $FILE"
Key Takeaways
- Never trust user input - always validate
- Check for empty strings with
-zand-n - Use regex patterns with
[[ =~ ]]for format validation - Validate files exist and are accessible before using them
- Check argument count at script start
- Sanitize input by removing dangerous characters
- Create reusable validation functions
- Provide clear error messages to users

