Bash Data Types
Learn how Bash handles strings, integers, arrays, associative arrays, and readonly variables with practical examples.
Everything Is a String
Unlike most programming languages, Bash has no strict type system. Every variable holds a string, and Bash interprets it differently depending on context — as a number in arithmetic expressions, as a path in file tests, or as plain text otherwise. Understanding this prevents a class of subtle bugs where you expect numeric behavior and get string behavior instead.
x=42
y="hello"
z=3.14
# All are strings — bash doesn't know the difference without hints
declare -p x # declare -- x="42"
declare -p y # declare -- y="hello"
declare -p z # declare -- z="3.14"
declare is the keyword that adds type attributes to variables, giving Bash hints about how to treat them.
Strings
String is the default type. No declaration is needed, and strings can contain anything — whitespace, special characters, and even newlines.
first="Alice"
last="Smith"
full="${first} ${last}"
echo "$full" # Alice Smith
# Multi-line string
message="Line one
Line two
Line three"
echo "$message"
# Heredoc — cleaner for multi-line content
message=$(cat <<'EOF'
Dear Alice,
Your account is active.
EOF
)
echo "$message"
String length:
name="Alice"
echo "${#name}" # 5 — the # operator counts characters
Integers
Use declare -i to mark a variable as an integer. Bash then performs arithmetic on assignment rather than string concatenation. This distinction matters: without the flag, count+=5 appends the character 5 to the string; with it, count is incremented numerically.
declare -i count=10
count+=5
echo "$count" # 15 (arithmetic addition)
count="abc" # invalid — bash stores 0 when the string is non-numeric
echo "$count" # 0
Without declare -i, assignments are literal string operations:
count=10
count+=5
echo "$count" # 105 (string concatenation — not addition!)
For arithmetic without declare -i, use the $(( )) construct:
a=10
b=3
echo $(( a + b )) # 13
echo $(( a * b )) # 30
echo $(( a / b )) # 3 (integer division — remainder discarded)
echo $(( a % b )) # 1 (remainder)
echo $(( a ** b )) # 1000 (exponentiation)
Indexed Arrays
Indexed arrays let you store ordered lists of values and refer to them by position. They are essential any time you need to process a collection of items — a list of servers to check, files to process, or arguments to pass to a command.
# Declaration and initialization in one step
fruits=("apple" "banana" "cherry")
# Or declare first, then assign
declare -a colors
colors[0]="red"
colors[1]="green"
colors[2]="blue"
# Access by index
echo "${fruits[0]}" # apple
echo "${fruits[1]}" # banana
echo "${fruits[-1]}" # cherry (negative index counts from the end)
# All elements — always use [@] in double quotes
echo "${fruits[@]}" # apple banana cherry
# Number of elements
echo "${#fruits[@]}" # 3
# All indices
echo "${!fruits[@]}" # 0 1 2
Adding and removing elements:
fruits+=("date") # append a new element
fruits[1]="blueberry" # replace element at index 1
unset fruits[2] # remove index 2 (leaves a gap in the indices)
echo "${fruits[@]}" # apple blueberry date
echo "${#fruits[@]}" # 3
Slicing arrays:
letters=("a" "b" "c" "d" "e")
echo "${letters[@]:1:3}" # b c d (start at index 1, take 3 elements)
echo "${letters[@]:2}" # c d e (from index 2 to end)
Associative Arrays
Associative arrays use string keys instead of integer indices — essentially a hash map or dictionary. They are declared with declare -A (the declaration is mandatory; without it, Bash treats them as indexed arrays and the string keys are silently converted to integers).
# Must declare explicitly — unlike indexed arrays
declare -A user
user["name"]="Alice"
user["age"]="30"
user["role"]="admin"
# Access
echo "${user["name"]}" # Alice
echo "${user[role]}" # admin (quotes optional for simple keys)
# All values
echo "${user[@]}" # Alice 30 admin (order is not guaranteed)
# All keys
echo "${!user[@]}" # name age role
# Check if key exists
if [[ -v user["email"] ]]; then
echo "email is set"
else
echo "email not set"
fi
Iterating over an associative array:
declare -A config=(
[host]="localhost"
[port]="5432"
[db]="myapp"
)
for key in "${!config[@]}"; do
echo "$key = ${config[$key]}"
done
# host = localhost
# port = 5432
# db = myapp
readonly
readonly prevents modification after initial assignment. It is useful for defining constants at the top of a script — values that represent configuration, paths, or limits that must never be changed. If something tries to overwrite them, the script exits with an error immediately.
readonly MAX_CONNECTIONS=100
readonly -a ALLOWED_ENVS=("production" "staging" "development")
MAX_CONNECTIONS=200 # Error: readonly variable
# declare -r is equivalent
declare -r API_VERSION="v2"
nameref — Variable References (Bash 4.3+)
declare -n creates a reference to another variable by name. Its main use is writing generic functions that can operate on any variable passed in by name — particularly useful for returning arrays from functions without a subshell.
declare -A server_a=([host]="web1" [port]="80")
declare -A server_b=([host]="web2" [port]="443")
print_server() {
declare -n ref="$1" # ref is now an alias for the named variable
echo "Host: ${ref[host]}, Port: ${ref[port]}"
}
print_server server_a # Host: web1, Port: 80
print_server server_b # Host: web2, Port: 443
Type Summary
| declare flag | Type | Example |
|---|---|---|
| (none) | String | name="Alice" |
-i | Integer | declare -i count=0 |
-a | Indexed array | declare -a list=() |
-A | Associative array | declare -A map=() |
-r | Readonly | declare -r MAX=100 |
-x | Exported | declare -x PATH="..." |
-n | Nameref | declare -n ref=varname |
-l | Lowercase on assign | declare -l lower |
-u | Uppercase on assign | declare -u upper |
What’s Next
With data types covered, the next tutorial digs into operators — arithmetic, comparison, and logical operators for building real decision logic.