Complete Guide to Bash Data Types

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

  1. Weak Typing: Variables can hold any type, context determines interpretation
  2. String Focus: Most data in Bash is handled as strings
  3. Integer Support: Arithmetic operations work on integer types
  4. Arrays: Both indexed and associative arrays available
  5. Type Attributes: declare can enforce certain behaviors
  6. Context Matters: How data is used determines its effective type

Type Summary

TypeDeclarationUsageFeatures
Stringvar="value"${var}Pattern matching, substitution
Integerdeclare -i$((var))Arithmetic operations
FloatN/AUse bcExternal tool required
Arraydeclare -a${arr[@]}Indexed by number
Associativedeclare -A${map[key]}Key-value pairs
Readonlydeclare -r$varConstant values
Exporteddeclare -x$varEnvironment variables

Best Practices

  1. Use declare for type enforcement when appropriate
  2. Validate input types before processing
  3. Use arrays for collections of items
  4. Use associative arrays for key-value data
  5. Handle type conversions explicitly
  6. Consider performance of type operations
  7. Use parameter expansion instead of external commands
  8. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper