Introduction to Bash Data Types
Bash is a weakly typed language, meaning variables don't have strict data types like in compiled languages (C, Java, Rust). However, Bash does support different data types through variable attributes and context-based interpretation. Understanding how Bash handles different types of data is crucial for writing robust scripts.
Key Concepts
- Weak Typing: Variables can hold any type of data
- Dynamic Typing: Type is determined by context and usage
- Variable Attributes: Declare can set specific behaviors
- Type Context: How data is interpreted depends on operation
- Implicit Conversion: Bash automatically converts between types as needed
1. Basic Data Types
Strings
#!/bin/bash
# Strings are the most common data type in Bash
name="John Doe"
greeting='Hello World'
empty_string=""
# String with spaces
multi_word="This is a string with spaces"
# String with special characters
special="Special chars: $ ` \" \\"
# String concatenation
first="Hello"
second="World"
combined="$first $second"
echo "$combined" # Hello World
# String length
str="Hello"
echo "Length: ${#str}" # 5
# Substring extraction
str="Hello World"
echo "${str:6}" # World (from position 6)
echo "${str:0:5}" # Hello (5 chars from position 0)
echo "${str: -5}" # World (last 5 chars)
# String replacement
str="Hello World"
echo "${str/World/Universe}" # Hello Universe
echo "${str//o/a}" # Hella Warld (replace all)
# Case modification
str="hello world"
echo "${str^^}" # HELLO WORLD (uppercase)
echo "${str^}" # Hello world (capitalize first)
echo "${str,,}" # hello world (lowercase)
Integers
#!/bin/bash # Integers are typically used in arithmetic contexts declare -i count=10 # Declare as integer count=5 # Still integer # count="hello" # Would set to 0 (invalid number) # Different integer bases declare -i dec=42 declare -i hex=0x2A declare -i oct=052 declare -i bin=2#101010 echo "Decimal: $dec" echo "Hexadecimal: $hex" echo "Octal: $oct" echo "Binary: $bin" # Integer operations a=10 b=3 # Arithmetic expansion sum=$((a + b)) diff=$((a - b)) prod=$((a * b)) quot=$((a / b)) rem=$((a % b)) pow=$((a ** b)) echo "a=$a, b=$b" echo "Sum: $sum" echo "Difference: $diff" echo "Product: $prod" echo "Quotient: $quot" echo "Remainder: $rem" echo "Power: $pow" # Increment/decrement x=5 ((x++)) # Post-increment echo "x: $x" # 6 ((++x)) # Pre-increment echo "x: $x" # 7 ((x+=5)) # Add 5 echo "x: $x" # 12 ((x-=3)) # Subtract 3 echo "x: $x" # 9 # Using let for arithmetic let y=10+5 echo "y: $y" let y+=3 echo "y: $y"
Floating Point Numbers
#!/bin/bash
# Bash doesn't natively support floating point
# Use bc (basic calculator) or awk
# Using bc for floating point
a=10.5
b=3.2
sum=$(echo "$a + $b" | bc)
diff=$(echo "$a - $b" | bc)
prod=$(echo "$a * $b" | bc)
quot=$(echo "scale=2; $a / $b" | bc)
echo "a=$a, b=$b"
echo "Sum: $sum"
echo "Difference: $diff"
echo "Product: $prod"
echo "Quotient: $quot"
# More complex bc operations
result=$(echo "scale=4; sqrt(25) + sin(0.5)" | bc -l)
echo "Complex result: $result"
# Using awk for floating point
result=$(awk "BEGIN {printf \"%.2f\", $a * $b}")
echo "Awk result: $result"
# Comparison with floating point
if (( $(echo "$a > $b" | bc -l) )); then
echo "$a is greater than $b"
fi
# Scientific notation
large=1.5e3
small=2.5e-2
echo "Large: $large"
echo "Small: $small"
Booleans
#!/bin/bash
# Bash doesn't have a boolean type
# Convention: use 0 for true, 1 for false (opposite of many languages)
# Test commands return 0 for success, non-zero for failure
# Boolean variables (using strings)
success="true"
failure="false"
if [ "$success" = "true" ]; then
echo "Operation succeeded"
fi
# Using exit codes as booleans
function check_file() {
if [ -f "$1" ]; then
return 0 # True (success)
else
return 1 # False (failure)
fi
}
if check_file "/etc/passwd"; then
echo "File exists"
fi
# Boolean expressions
# && for AND, || for OR
if [ -f "/etc/passwd" ] && [ -r "/etc/passwd" ]; then
echo "File exists and is readable"
fi
if [ -f "/etc/passwd" ] || [ -f "/etc/shadow" ]; then
echo "At least one file exists"
fi
# Using arithmetic for boolean logic
true_val=1
false_val=0
if (( true_val )); then
echo "This executes (1 is true)"
fi
if (( ! false_val )); then
echo "This executes (not false is true)"
fi
2. Variable Attributes with declare
declare Options
#!/bin/bash
# declare -i: Integer attribute
declare -i number=42
number="25" # Valid
number="abc" # Becomes 0 (invalid number)
echo "Number: $number"
# declare -r: Read-only (constant)
declare -r PI=3.14159
# PI=3.14 # Error: readonly variable
# declare -l: Convert to lowercase
declare -l lowercase="HELLO WORLD"
echo "$lowercase" # hello world
# declare -u: Convert to uppercase
declare -u uppercase="hello world"
echo "$uppercase" # HELLO WORLD
# declare -a: Array
declare -a fruits=("apple" "banana" "cherry")
# declare -A: Associative array
declare -A user=(
["name"]="John"
["age"]="30"
["city"]="New York"
)
# declare -x: Export variable
declare -x DATABASE_URL="postgres://localhost:5432/db"
# Now available to child processes
# declare -p: Print attributes
declare -p PI
declare -p lowercase
# Multiple attributes
declare -r -i MAX_RETRIES=5
# or
declare -ri MAX_CONNECTIONS=100
# Show all declared variables
declare -p
Type Declaration Examples
#!/bin/bash
# Integer with default value
declare -i count=0
for i in {1..5}; do
((count++))
done
echo "Count: $count"
# Read-only configuration
declare -r CONFIG_FILE="/etc/myapp/config.conf"
declare -r LOG_DIR="/var/log/myapp"
declare -r MAX_LOG_SIZE=10485760
# Case-insensitive string
declare -l input="HELLO"
if [ "$input" = "hello" ]; then
echo "Matched case-insensitively"
fi
# Export for child processes
declare -x PATH="/custom/bin:$PATH"
declare -x MYAPP_ENV="production"
# Combining attributes
declare -ri HTTP_PORT=8080
# HTTP_PORT=9090 # Error: read-only
# Array with declare
declare -a colors=("red" "green" "blue")
declare -p colors
3. Arrays
Indexed Arrays
#!/bin/bash
# Creating indexed arrays
fruits=("apple" "banana" "cherry")
numbers=([0]=10 [1]=20 [2]=30)
mixed=("hello" 42 3.14 "world")
# Adding elements
fruits[3]="date"
fruits+=("elderberry" "fig") # Append multiple
# Accessing elements
echo "First fruit: ${fruits[0]}"
echo "Last fruit: ${fruits[-1]}"
echo "All fruits: ${fruits[@]}"
echo "All fruits (as string): ${fruits[*]}"
# Array length
echo "Number of fruits: ${#fruits[@]}"
# Array indices
echo "Indices: ${!fruits[@]}"
# Looping through array
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
# Looping with indices
for i in "${!fruits[@]}"; do
echo "Index $i: ${fruits[$i]}"
done
# Slicing arrays
subset=("${fruits[@]:1:3}") # Elements 1-3
echo "Subset: ${subset[@]}"
# Removing elements
unset "fruits[1]" # Remove element at index 1
echo "After removal: ${fruits[@]}"
# Replace element
fruits[0]="apricot"
echo "After replace: ${fruits[@]}"
# Check if element exists
if [ -n "${fruits[2]+x}" ]; then
echo "Element at index 2 exists"
fi
# Copy array
new_array=("${fruits[@]}")
# Join array elements
joined=$(IFS=, ; echo "${fruits[*]}")
echo "Joined: $joined"
Associative Arrays (Hash Maps)
#!/bin/bash # Declare associative array declare -A user declare -A config=( ["host"]="localhost" ["port"]="8080" ["user"]="admin" ) # Adding key-value pairs user["name"]="John Doe" user["email"]="[email protected]" user["age"]=30 user["active"]=true # Accessing values echo "Name: ${user[name]}" # Quotes optional for simple keys echo "Email: ${user[email]}" echo "All values: ${user[@]}" echo "All keys: ${!user[@]}" # Number of entries echo "User has ${#user[@]} fields" # Looping through associative array for key in "${!user[@]}"; do echo "Key: $key, Value: ${user[$key]}" done # Check if key exists if [[ -v user["email"] ]]; then echo "Email key exists" fi # Alternative check if [ -n "${user["phone"]+x}" ]; then echo "Phone exists" else echo "Phone does not exist" fi # Remove key-value pair unset user["age"] # Nested structures (simulated) declare -A users users["john,name"]="John Doe" users["john,email"]="[email protected]" users["jane,name"]="Jane Smith" users["jane,email"]="[email protected]" # Access nested-like data echo "John's email: ${users["john,email"]}" # Merge associative arrays declare -A defaults=( ["host"]="localhost" ["port"]="8080" ["debug"]="false" ) declare -A overrides=( ["port"]="9090" ["user"]="custom" ) # Merge (override takes precedence) declare -A merged for key in "${!defaults[@]}"; do merged[$key]="${defaults[$key]}" done for key in "${!overrides[@]}"; do merged[$key]="${overrides[$key]}" done
4. Advanced String Operations
String Manipulation
#!/bin/bash
# Pattern matching
str="hello_world_example.txt"
# Remove shortest prefix pattern
echo "${str#*_}" # world_example.txt
echo "${str#*_*_}" # example.txt
# Remove longest prefix pattern
echo "${str##*_}" # txt
# Remove shortest suffix pattern
echo "${str%_*}" # hello_world_example
echo "${str%_*_*}" # hello_world
# Remove longest suffix pattern
echo "${str%%.*}" # hello_world_example
# Search and replace
str="apple apple apple"
echo "${str/apple/orange}" # orange apple apple
echo "${str//apple/orange}" # orange orange orange
echo "${str/#apple/orange}" # orange apple apple (start)
echo "${str/%apple/orange}" # apple apple orange (end)
# Case conversion
str="Hello World"
echo "${str,,}" # hello world
echo "${str,}" # hello World (first only)
echo "${str^^}" # HELLO WORLD
echo "${str^}" # Hello World (first only)
# Default values
unset var
echo "${var:-default}" # default (if unset)
echo "${var:=default}" # default (assigns if unset)
echo "${var:+exists}" # (empty) - uses if set
echo "${var:?error}" # error (if unset)
# String length
str="Hello"
echo "${#str}" # 5
# Substring with pattern
str="abc123def456"
echo "${str//[0-9]/}" # abcdef (remove digits)
echo "${str//[a-z]/}" # 123456 (remove letters)
Regular Expressions
#!/bin/bash # Using =~ for regex matching email="[email protected]" if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then echo "Valid email format" fi # Capturing groups phone="123-456-7890" if [[ "$phone" =~ ^([0-9]{3})-([0-9]{3})-([0-9]{4})$ ]]; then echo "Area code: ${BASH_REMATCH[1]}" echo "Prefix: ${BASH_REMATCH[2]}" echo "Line number: ${BASH_REMATCH[3]}" fi # Multiple matches text="The numbers are 123, 456, and 789" pattern="[0-9]+" while [[ "$text" =~ $pattern ]]; do echo "Found: ${BASH_REMATCH[0]}" text="${text#*"${BASH_REMATCH[0]}"}" done # Validating input validate_input() { local input="$1" local type="$2" case "$type" in email) [[ "$input" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] ;; phone) [[ "$input" =~ ^[0-9]{3}-[0-9]{3}-[0-9]{4}$ ]] ;; ip) [[ "$input" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] ;; date) [[ "$input" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] ;; esac } if validate_input "[email protected]" "email"; then echo "Valid email" fi
5. Numbers and Arithmetic
Integer Operations
#!/bin/bash # Basic arithmetic a=10 b=3 # Different arithmetic syntaxes echo $((a + b)) echo $[a + b] # Older syntax ((result = a + b)) echo "$result" # Increment/decrement ((a++)) ((b--)) ((a += 5)) ((b *= 2)) # Bitwise operations x=12 # 1100 in binary y=10 # 1010 in binary echo "x & y: $((x & y))" # 8 (1000) echo "x | y: $((x | y))" # 14 (1110) echo "x ^ y: $((x ^ y))" # 6 (0110) echo "~x: $((~x))" # -13 (two's complement) echo "x << 1: $((x << 1))" # 24 (11000) echo "x >> 1: $((x >> 1))" # 6 (0110) # Logical operations x=5 y=0 echo "x && y: $((x && y))" # 0 (false) echo "x || y: $((x || y))" # 1 (true) echo "!x: $((!x))" # 0 (false) echo "!y: $((!y))" # 1 (true) # Ternary-like operation result=$((a > b ? a : b)) echo "Max: $result" # Assignment in arithmetic ((count=0)) ((count++)) ((count+=5)) ((count=count*2)) echo "Count: $count"
Advanced Math with bc
#!/bin/bash
# Basic bc operations
echo "scale=2; 10 / 3" | bc
# Variables in bc
x=5.5
y=2.3
result=$(bc << EOF
scale=4
x = $x
y = $y
x * y + sqrt(x)
EOF
)
echo "Result: $result"
# Math functions
cat << EOF | bc -l
scale=4
s = s(0.5) # sine
c = c(0.5) # cosine
l = l(2.71828) # natural log
e = e(1) # exponential
a = a(1) # arctangent
print "sin(0.5) = ", s, "\n"
print "cos(0.5) = ", c, "\n"
print "ln(e) = ", l, "\n"
print "e^1 = ", e, "\n"
print "atan(1) = ", a, "\n"
EOF
# Conditional in bc
check_range() {
local value="$1"
local min="$2"
local max="$3"
bc << EOF
if ($value >= $min && $value <= $max) 1 else 0
EOF
}
if [ "$(check_range 5.5 0 10)" -eq 1 ]; then
echo "Value in range"
fi
# Loop in bc
bc << EOF
for (i=1; i<=5; i++) {
print "Square of ", i, " = ", i^2, "\n"
}
EOF
# Custom math functions
bc << EOF
define f(x) {
return (x * x + 2 * x + 1)
}
for (i=0; i<=5; i++) {
print "f(", i, ") = ", f(i), "\n"
}
EOF
6. Type Conversion
Implicit vs Explicit Conversion
#!/bin/bash # Implicit conversion in arithmetic str="42" num=$((str + 10)) # String to integer echo "$num" # 52 # Implicit in comparisons if [[ "10" -eq 10 ]]; then # String to integer echo "Equal" fi # Explicit conversion # String to integer int_value=$((42)) int_value=$(echo "10" | bc) # Integer to string str_value="$((42))" str_value=$(printf "%d" 42) # Float to string float_str=$(printf "%.2f" 3.14159) # Base conversion # Decimal to hex dec=255 hex=$(printf "%x" "$dec") echo "Hex: $hex" # Hex to decimal hex="FF" dec=$((16#$hex)) echo "Decimal: $dec" # Binary to decimal binary=1010 dec=$((2#$binary)) echo "Decimal: $dec" # ASCII to character ascii=65 char=$(printf "\\$(printf "%03o" "$ascii")") echo "Char: $char" # Character to ASCII char='A' ascii=$(printf "%d" "'$char") echo "ASCII: $ascii"
printf for Type Formatting
#!/bin/bash # printf for formatted output printf "Integer: %d\n" 42 printf "Float: %.2f\n" 3.14159 printf "String: %s\n" "hello" printf "Hex: %x\n" 255 printf "Octal: %o\n" 255 printf "Character: %c\n" "A" # Width and alignment printf "|%10s|\n" "right" # Right aligned printf "|%-10s|\n" "left" # Left aligned printf "|%10.2f|\n" 3.14159 # Width and precision # Multiple arguments printf "Name: %s, Age: %d\n" "John" 30 # Zero padding printf "ID: %05d\n" 42 # 00042 # Sign printf "%+d\n" 42 # +42 printf "%+d\n" -42 # -42 # Space padding printf "% d\n" 42 # " 42" printf "% d\n" -42 # "-42" # Printf into variable name=$(printf "User_%03d" 5) echo "$name" # User_005 # Complex formatting printf "|%-10s|%8s|%8s|\n" "Name" "Age" "Score" printf "|%-10s|%8d|%8.1f|\n" "John" 30 95.5 printf "|%-10s|%8d|%8.1f|\n" "Jane" 28 97.8
7. Type Checking and Testing
Testing Variable Types
#!/bin/bash
# Check if variable is set
if [ -v myvar ]; then
echo "myvar is set"
fi
# Check if variable is empty
if [ -z "$myvar" ]; then
echo "myvar is empty or unset"
fi
# Check if variable is non-empty
if [ -n "$myvar" ]; then
echo "myvar has content"
fi
# Check if value is integer
is_integer() {
[[ "$1" =~ ^-?[0-9]+$ ]]
}
# Check if value is float
is_float() {
[[ "$1" =~ ^-?[0-9]*\.?[0-9]+$ ]]
}
# Check if value is alphabetic
is_alpha() {
[[ "$1" =~ ^[a-zA-Z]+$ ]]
}
# Check if value is alphanumeric
is_alnum() {
[[ "$1" =~ ^[a-zA-Z0-9]+$ ]]
}
# Test functions
test_values() {
local values=("42" "-10" "3.14" "-2.5" "abc" "123abc" "" " " "$@")
for val in "${values[@]}"; do
echo "Testing '$val':"
echo " Integer? $(is_integer "$val" && echo yes || echo no)"
echo " Float? $(is_float "$val" && echo yes || echo no)"
echo " Alpha? $(is_alpha "$val" && echo yes || echo no)"
echo " Alnum? $(is_alnum "$val" && echo yes || echo no)"
echo
done
}
# Run tests
test_values
Type Attributes Introspection
#!/bin/bash
# Check if variable has integer attribute
declare -i int_var=42
str_var="hello"
if [[ "$(declare -p int_var 2>/dev/null)" == *"declare -i"* ]]; then
echo "int_var is integer"
fi
# Check if variable is array
declare -a array_var=(1 2 3)
if [[ "$(declare -p array_var 2>/dev/null)" == *"declare -a"* ]]; then
echo "array_var is indexed array"
fi
# Check if variable is associative array
declare -A assoc_var=([key]=value)
if [[ "$(declare -p assoc_var 2>/dev/null)" == *"declare -A"* ]]; then
echo "assoc_var is associative array"
fi
# Check if variable is readonly
declare -r readonly_var="cannot change"
if [[ "$(declare -p readonly_var 2>/dev/null)" == *"declare -r"* ]]; then
echo "readonly_var is readonly"
fi
# Function to get variable type
get_type() {
local var_name="$1"
local decl=$(declare -p "$var_name" 2>/dev/null)
if [[ -z "$decl" ]]; then
echo "unset"
elif [[ "$decl" == *"declare -A"* ]]; then
echo "associative array"
elif [[ "$decl" == *"declare -a"* ]]; then
echo "indexed array"
elif [[ "$decl" == *"declare -i"* ]]; then
echo "integer"
elif [[ "$decl" == *"declare -r"* ]]; then
echo "readonly"
elif [[ "$decl" == *"declare -x"* ]]; then
echo "exported"
else
echo "string"
fi
}
# Test type detection
declare -i num=42
declare -a arr=(1 2 3)
declare -A map=([k]=v)
declare -r ro="constant"
declare -x exp="exported"
normal="value"
for var in num arr map ro exp normal; do
echo "$var: $(get_type "$var")"
done
8. Special Types and Structures
Here Documents
#!/bin/bash # Basic here document cat << EOF This is a here document It can contain multiple lines Variables like $HOME are expanded EOF # Here document with no variable expansion cat << 'EOF' Variables like $HOME are NOT expanded Commands like `date` are NOT executed EOF # Here document with tabs ignored cat <<- EOF This line starts with tabs They will be removed Useful for indented code EOF # Here string cat <<< "This is a here string" # Assign here document to variable sql=$(cat << EOF SELECT * FROM users WHERE active = true ORDER BY name; EOF ) echo "$sql" # Here document with command bc << EOF scale=2 a = 5.5 b = 3.2 a * b EOF # Multi-line string with here doc message=$(cat << 'EOF' This is a multi-line message EOF ) echo "$message"
Process Substitution
#!/bin/bash
# Process substitution for comparing outputs
diff <(ls /tmp) <(ls /var/tmp)
# Using process substitution with while
while read line; do
echo "Processed: $line"
done < <(find . -name "*.txt" -type f)
# Multiple process substitutions
paste <(ls -1) <(ls -1 | wc -l)
# Redirect both stdout and stderr
command < <(echo input) 2> >(tee error.log >&2)
# Process substitution with arrays
mapfile -t files < <(find . -type f -name "*.sh")
echo "Found ${#files[@]} shell scripts"
# Complex example
while IFS= read -r user; do
while IFS= read -r process; do
echo "$user: $process"
done < <(ps -u "$user" -o comm=)
done < <(cut -d: -f1 /etc/passwd | head -5)
9. Type Declaration and Best Practices
Declaring Variables with Types
#!/bin/bash
# Best practices for type declaration
# Constants
readonly MAX_RETRIES=3
readonly CONFIG_FILE="/etc/myapp.conf"
readonly -a VALID_OPTIONS=("start" "stop" "restart")
# Configuration with defaults
declare -A CONFIG=(
["host"]="localhost"
["port"]="8080"
["debug"]="false"
)
# Export environment variables
export DATABASE_URL="postgresql://localhost/mydb"
export PATH="/custom/bin:$PATH"
# Integer counters
declare -i error_count=0
declare -i total_processed=0
# Arrays for collections
declare -a users=()
declare -a processes=()
# Read-only arrays
readonly -a DAYS=("Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun")
# Typed functions
function get_integer() {
local -i result
result=$(( $1 * 2 ))
echo "$result"
}
function get_string() {
local result
result="Processed: $1"
echo "$result"
}
# Usage
value=$(get_integer 5)
message=$(get_string "test")
echo "$value, $message"
Type Safety Patterns
#!/bin/bash
# Type checking functions
assert_integer() {
if ! [[ "$1" =~ ^-?[0-9]+$ ]]; then
echo "Error: Expected integer, got: $1" >&2
return 1
fi
return 0
}
assert_positive() {
if ! assert_integer "$1" || [ "$1" -le 0 ]; then
echo "Error: Expected positive integer" >&2
return 1
fi
return 0
}
assert_string() {
if [ -z "$1" ]; then
echo "Error: Expected non-empty string" >&2
return 1
fi
return 0
}
assert_array() {
local -n arr=$1
if [[ ! "$(declare -p $1 2>/dev/null)" == *"declare -a"* ]]; then
echo "Error: Expected array" >&2
return 1
fi
return 0
}
# Safe arithmetic with validation
safe_add() {
assert_integer "$1" || return 1
assert_integer "$2" || return 1
echo $(( $1 + $2 ))
}
# Safe string operations
safe_upper() {
assert_string "$1" || return 1
echo "${1^^}"
}
# Usage with error handling
result=$(safe_add 5 10) || exit 1
echo "Sum: $result"
upper=$(safe_upper "hello") || exit 1
echo "Upper: $upper"
10. Practical Examples
Data Validation Functions
#!/bin/bash
# Comprehensive validation library
# Type validators
validate_email() {
local email="$1"
[[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
}
validate_phone() {
local phone="$1"
# Strip common formatting
phone="${phone//[^0-9]/}"
[[ ${#phone} -eq 10 ]] || [[ ${#phone} -eq 11 && "${phone:0:1}" == "1" ]]
}
validate_ip() {
local ip="$1"
local IFS='.'
read -ra octets <<< "$ip"
if [ ${#octets[@]} -ne 4 ]; then
return 1
fi
for octet in "${octets[@]}"; do
if ! [[ "$octet" =~ ^[0-9]+$ ]] || [ "$octet" -lt 0 ] || [ "$octet" -gt 255 ]; then
return 1
fi
if [ "${#octet}" -gt 1 ] && [ "${octet:0:1}" = "0" ]; then
return 1 # No leading zeros
fi
done
return 0
}
validate_date() {
local date="$1"
local format="${2:-%Y-%m-%d}"
if command -v date >/dev/null 2>&1; then
if date -d "$date" "+$format" >/dev/null 2>&1; then
return 0
fi
fi
return 1
}
validate_url() {
local url="$1"
[[ "$url" =~ ^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$ ]]
}
validate_credit_card() {
local cc="${1//[^0-9]/}"
# Luhn algorithm
local sum=0
local len=${#cc}
local parity=$((len % 2))
for ((i=0; i<len; i++)); do
local digit=${cc:$i:1}
if [ $((i % 2)) -eq $parity ]; then
digit=$((digit * 2))
[ $digit -gt 9 ] && digit=$((digit - 9))
fi
sum=$((sum + digit))
done
[ $((sum % 10)) -eq 0 ]
}
# Test validators
test_validator() {
local validator="$1"
shift
echo "Testing $validator:"
for value in "$@"; do
if "$validator" "$value"; then
echo " ✓ $value"
else
echo " ✗ $value"
fi
done
echo
}
# Run tests
test_validator validate_email \
"[email protected]" \
"invalid.email" \
"[email protected]" \
"[email protected]"
test_validator validate_phone \
"555-123-4567" \
"5551234567" \
"1-555-123-4567" \
"123" \
"555-123-45678"
test_validator validate_ip \
"192.168.1.1" \
"256.0.0.0" \
"1.2.3.4" \
"01.02.03.04" \
"999.999.999.999"
Type Conversion Library
#!/bin/bash
# Comprehensive type conversion library
# String to number conversions
str_to_int() {
local str="$1"
local default="${2:-0}"
# Remove non-numeric characters except leading minus
str="${str//[^0-9-]/}"
if [[ "$str" =~ ^-?[0-9]+$ ]]; then
echo "$str"
else
echo "$default"
fi
}
str_to_float() {
local str="$1"
local default="${2:-0}"
# Keep digits, decimal point, and leading minus
str=$(echo "$str" | sed 's/[^0-9.-]//g')
if [[ "$str" =~ ^-?[0-9]*\.?[0-9]+$ ]]; then
echo "$str"
else
echo "$default"
fi
}
# Number to string conversions
int_to_hex() {
printf "%x" "$1"
}
int_to_oct() {
printf "%o" "$1"
}
int_to_bin() {
local num="$1"
local bin=""
while [ "$num" -gt 0 ]; do
bin="$((num & 1))$bin"
num="$((num >> 1))"
done
echo "${bin:-0}"
}
float_to_str() {
local float="$1"
local precision="${2:-2}"
printf "%.${precision}f" "$float" 2>/dev/null || echo "0"
}
# Unit conversions
bytes_to_human() {
local bytes="$1"
local units=("B" "KB" "MB" "GB" "TB" "PB")
local unit=0
while [ "$bytes" -ge 1024 ] && [ $unit -lt 5 ]; do
bytes=$((bytes / 1024))
unit=$((unit + 1))
done
echo "$bytes ${units[$unit]}"
}
human_to_bytes() {
local human="$1"
local number=$(echo "$human" | grep -o '^[0-9.]*')
local unit=$(echo "$human" | grep -o '[KMGTP]B\?$')
case "$unit" in
B|b) factor=1 ;;
KB|Kb) factor=1024 ;;
MB|Mb) factor=$((1024 * 1024)) ;;
GB|Gb) factor=$((1024 * 1024 * 1024)) ;;
TB|Tb) factor=$((1024 * 1024 * 1024 * 1024)) ;;
*) factor=1 ;;
esac
echo "$(echo "$number * $factor" | bc 2>/dev/null || echo 0)"
}
# Temperature conversions
celsius_to_fahrenheit() {
echo "scale=2; $1 * 9/5 + 32" | bc
}
fahrenheit_to_celsius() {
echo "scale=2; ($1 - 32) * 5/9" | bc
}
celsius_to_kelvin() {
echo "scale=2; $1 + 273.15" | bc
}
# Test conversions
echo "str_to_int '42abc': $(str_to_int '42abc')"
echo "str_to_float '3.14xyz': $(str_to_float '3.14xyz')"
echo "int_to_hex 255: $(int_to_hex 255)"
echo "int_to_bin 42: $(int_to_bin 42)"
echo "bytes_to_human 1048576: $(bytes_to_human 1048576)"
echo "human_to_bytes '1.5 GB': $(human_to_bytes '1.5 GB')"
echo "25°C to °F: $(celsius_to_fahrenheit 25)°F"
11. Performance Considerations
Type Impact on Performance
#!/bin/bash
# Benchmark different operations
benchmark() {
local iterations="${1:-10000}"
echo "Benchmark with $iterations iterations"
echo "==================================="
# String concatenation
start=$SECONDS
str=""
for ((i=0; i<iterations; i++)); do
str+="a"
done
echo "String concatenation: $((SECONDS - start))s"
# Integer arithmetic
start=$SECONDS
sum=0
for ((i=0; i<iterations; i++)); do
((sum += i))
done
echo "Integer arithmetic: $((SECONDS - start))s"
# Array operations
start=$SECONDS
arr=()
for ((i=0; i<iterations; i++)); do
arr+=($i)
done
echo "Array append: $((SECONDS - start))s"
# String operations
start=$SECONDS
text="Hello World"
for ((i=0; i<iterations; i++)); do
upper="${text^^}"
lower="${text,,}"
done
echo "String operations: $((SECONDS - start))s"
}
# Memory usage
check_memory() {
local var_name="$1"
local var_value="$2"
# This is approximate
eval "$var_name=\"$var_value\""
# Get process memory before
mem_before=$(ps -o rss= -p $$)
# Force variable into memory
eval "length=\${#$var_name}"
# Get process memory after
mem_after=$(ps -o rss= -p $$)
echo "Memory used by $var_name: $((mem_after - mem_before)) KB"
}
# Test memory usage
check_memory "small" "Hello"
check_memory "medium" "$(printf 'x%.0s' {1..1000})"
check_memory "large" "$(printf 'x%.0s' {1..100000})"
Optimization Tips
#!/bin/bash
# Use integer types for counters
# Slow
count=0
for i in {1..1000}; do
count=$((count + 1))
done
# Fast
declare -i count=0
for i in {1..1000}; do
((count++))
done
# Avoid unnecessary forks
# Slow - forks subshell
length=$(echo -n "$str" | wc -c)
# Fast - uses parameter expansion
length=${#str}
# Use arrays for collections
# Slow - using strings
items="a b c d e"
for item in $items; do
process "$item"
done
# Fast - using arrays
items=("a" "b" "c" "d" "e")
for item in "${items[@]}"; do
process "$item"
done
# Avoid unnecessary external commands
# Slow
upper=$(echo "$str" | tr '[:lower:]' '[:upper:]')
# Fast (bash 4+)
upper="${str^^}"
# Use built-in pattern matching
# Slow
if echo "$str" | grep -q "^[0-9]\+$"; then
echo "Is number"
fi
# Fast
if [[ "$str" =~ ^[0-9]+$ ]]; then
echo "Is number"
fi
# Batch operations
# Slow - one fork per file
for file in *.txt; do
wc -l "$file"
done
# Fast - one fork for all files
wc -l *.txt
12. Common Patterns and Best Practices
Type Declaration Patterns
#!/bin/bash
# Configuration with defaults
: "${APP_NAME:=MyApp}"
: "${APP_PORT:=8080}"
: "${APP_DEBUG:=false}"
# Type-safe configuration
declare -A CONFIG=(
[name]="${APP_NAME}"
[port]="${APP_PORT}"
[debug]="${APP_DEBUG}"
)
# Validation function
validate_config() {
local errors=()
# Validate port is integer
if ! [[ "${CONFIG[port]}" =~ ^[0-9]+$ ]]; then
errors+=("Port must be integer")
fi
# Validate debug is boolean
if [[ ! "${CONFIG[debug]}" =~ ^(true|false)$ ]]; then
errors+=("Debug must be true/false")
fi
# Report errors
if [ ${#errors[@]} -gt 0 ]; then
printf '%s\n' "${errors[@]}"
return 1
fi
return 0
}
# Data structures
declare -a queue=() # FIFO queue
declare -a stack=() # LIFO stack
# Queue operations
queue_push() {
queue+=("$1")
}
queue_pop() {
if [ ${#queue[@]} -gt 0 ]; then
local first="${queue[0]}"
queue=("${queue[@]:1}")
echo "$first"
fi
}
# Stack operations
stack_push() {
stack+=("$1")
}
stack_pop() {
if [ ${#stack[@]} -gt 0 ]; then
local last_idx=$(( ${#stack[@]} - 1 ))
local last="${stack[$last_idx]}"
unset "stack[$last_idx]"
echo "$last"
fi
}
Error Handling with Types
#!/bin/bash
# Error types
declare -A ERROR_CODES=(
[SUCCESS]=0
[INVALID_INPUT]=1
[FILE_NOT_FOUND]=2
[PERMISSION_DENIED]=3
[CONFIG_ERROR]=4
)
# Error messages
declare -A ERROR_MESSAGES=(
[${ERROR_CODES[SUCCESS]}]="Success"
[${ERROR_CODES[INVALID_INPUT]}]="Invalid input"
[${ERROR_CODES[FILE_NOT_FOUND]}]="File not found"
[${ERROR_CODES[PERMISSION_DENIED]}]="Permission denied"
[${ERROR_CODES[CONFIG_ERROR]}]="Configuration error"
)
# Function returning typed error
read_config() {
local config_file="$1"
if [ ! -f "$config_file" ]; then
return ${ERROR_CODES[FILE_NOT_FOUND]}
fi
if [ ! -r "$config_file" ]; then
return ${ERROR_CODES[PERMISSION_DENIED]}
fi
# Process config...
return ${ERROR_CODES[SUCCESS]}
}
# Handle error with type
read_config "/etc/myapp.conf"
result=$?
if [ $result -ne 0 ]; then
echo "Error: ${ERROR_MESSAGES[$result]}"
exit $result
fi
Conclusion
Bash's approach to data types is flexible but requires understanding:
Key Takeaways
- Weak Typing: Variables can hold any type, context determines interpretation
- String Focus: Most data in Bash is handled as strings
- Integer Support: Arithmetic operations work on integer types
- Arrays: Both indexed and associative arrays available
- Type Attributes:
declarecan enforce certain behaviors - Context Matters: How data is used determines its effective type
Type Summary
| Type | Declaration | Usage | Features |
|---|---|---|---|
| String | var="value" | ${var} | Pattern matching, substitution |
| Integer | declare -i | $((var)) | Arithmetic operations |
| Float | N/A | Use bc | External tool required |
| Array | declare -a | ${arr[@]} | Indexed by number |
| Associative | declare -A | ${map[key]} | Key-value pairs |
| Readonly | declare -r | $var | Constant values |
| Exported | declare -x | $var | Environment variables |
Best Practices
- Use
declarefor type enforcement when appropriate - Validate input types before processing
- Use arrays for collections of items
- Use associative arrays for key-value data
- Handle type conversions explicitly
- Consider performance of type operations
- Use parameter expansion instead of external commands
- Document expected types in functions
Understanding Bash's data type system, even though it's weakly typed, helps write more robust and predictable scripts. While you don't need to declare types most of the time, knowing how to use type attributes and when to validate types can prevent many common scripting errors.