Table of Contents
- What Are Bash Arrays?
- Types of Bash Arrays
- Declaring and Initializing Arrays
- Accessing Array Elements
- Modifying Arrays: Adding, Updating, and Deleting Elements
- Array Length and Element Size
- Looping Through Arrays
- Advanced Array Operations
- Associative Arrays in Depth
- Practical Examples
- Common Pitfalls and Best Practices
- Conclusion
- References
What Are Bash Arrays?
A Bash array is a variable that can hold multiple values, each identified by an index (for indexed arrays) or a key (for associative arrays). Unlike regular Bash variables, which store a single string, arrays let you group related data, such as lists of filenames, user inputs, or configuration options.
Arrays were introduced in Bash 3.0 (2004) and have been expanded in later versions (e.g., associative arrays in Bash 4.0). They are supported by all modern Linux distributions and macOS (though macOS uses Bash 3.2 by default; see Pitfalls for details).
Types of Bash Arrays
Bash supports two primary types of arrays:
1. Indexed Arrays
The default array type, where elements are accessed via numeric indices (starting at 0 by default). Indices are integers, and elements are stored in contiguous or non-contiguous positions (sparse arrays).
2. Associative Arrays
Introduced in Bash 4.0, associative arrays use string keys instead of numeric indices. They are ideal for storing key-value pairs (e.g., dictionaries or hash maps).
3. Sparse Arrays
A subset of indexed arrays where indices are non-consecutive (e.g., arr[0]=a; arr[2]=b skips index 1). Sparse arrays are useful for scenarios where you need to map arbitrary integers to values, but they can complicate iteration.
Declaring and Initializing Arrays
Indexed Arrays
There are several ways to declare and initialize indexed arrays:
Method 1: Implicit Declaration
You can initialize an array directly without prior declaration:
# Space-separated values in parentheses; elements with spaces must be quoted
fruits=("apple" "banana" "cherry" "date")
Method 2: Explicit Declaration with declare -a
Use declare -a to explicitly define an indexed array (optional but improves readability):
declare -a cars # Declare array
cars=("Toyota" "Honda" "Ford") # Initialize later
Method 3: Individual Element Assignment
Assign elements to specific indices after declaration:
colors=() # Empty array
colors[0]="red"
colors[1]="green"
colors[3]="blue" # Creates a sparse array (index 2 is undefined)
Associative Arrays
Associative arrays require explicit declaration with declare -A (Bash 4.0+):
Method 1: Key-Value Initialization
declare -A capitals # Declare associative array
capitals=(
["France"]="Paris"
["Germany"]="Berlin"
["Japan"]="Tokyo"
)
Method 2: Dynamic Key Assignment
declare -A user # Empty associative array
user["name"]="Alice"
user["age"]="30"
user["city"]="New York"
Accessing Array Elements
Accessing Indexed Array Elements
Use ${array[index]} to retrieve a single element:
fruits=("apple" "banana" "cherry")
echo ${fruits[0]} # Output: apple (first element)
echo ${fruits[2]} # Output: cherry (third element)
Accessing All Elements
To access all elements of an array, use ${array[@]} or ${array[*]}:
${array[@]}: Expands to individual elements (preserves spaces in elements when quoted).${array[*]}: Expands to a single string with elements joined by the first character ofIFS(default: space).
Example:
echo "Using @: ${fruits[@]}" # Output: apple banana cherry
echo "Using *: ${fruits[*]}" # Output: apple banana cherry (same here, but see quoting below)
# Quoted @ preserves spaces in elements; quoted * does not
words=("hello world" "foo bar")
echo "Quoted @: ${words[@]}" # Output: hello world foo bar (two elements)
echo "Quoted *: ${words[*]}" # Output: hello world foo bar (single string, joined by space)
Accessing Ranges of Elements
Use ${array[@]:start:count} to slice an array (start at start index, return count elements):
numbers=(0 1 2 3 4 5)
echo ${numbers[@]:1:3} # Output: 1 2 3 (start at index 1, return 3 elements)
echo ${numbers[@]: -2} # Output: 4 5 (negative start = offset from end)
Accessing Associative Array Elements
Use ${array[key]} to retrieve values by key:
echo ${capitals["France"]} # Output: Paris
echo ${user["name"]} # Output: Alice
Modifying Arrays
Adding Elements
Indexed Arrays
- Append to end: Use
+=to add elements to the end of the array:fruits+=("elderberry" "fig") # Now fruits has 6 elements - Insert at index: Assign to a specific index (overwrites if the index exists):
fruits[2]="coconut" # Replaces "cherry" with "coconut"
Associative Arrays
Add new key-value pairs by assignment:
capitals["Italy"]="Rome" # New key "Italy" with value "Rome"
Updating Elements
Overwrite existing elements by reassigning their index/key:
# Indexed array
fruits[0]="apricot" # Update first element to "apricot"
# Associative array
capitals["Germany"]="Berlin (Capital)" # Update value for "Germany"
Deleting Elements
Use unset to remove elements or entire arrays:
# Delete indexed array element
unset fruits[1] # Removes "banana" (index 1)
# Delete associative array element
unset capitals["Japan"] # Removes key "Japan"
# Clear entire array
unset fruits # Deletes the array itself
Array Length and Element Size
Total Number of Elements
Use ${#array[@]} to get the length of an array (number of elements):
fruits=("apple" "banana" "cherry")
echo ${#fruits[@]} # Output: 3 (3 elements)
Length of a Specific Element
Use ${#array[index]} to get the character count of an element:
echo ${#fruits[0]} # Output: 5 ("apple" has 5 characters)
Associative Array Size
Same syntax applies to associative arrays:
echo ${#capitals[@]} # Output: 2 (after deleting "Japan" earlier)
Looping Through Arrays
Looping is critical for processing array elements. Use these patterns:
Looping Through Indexed Arrays
Method 1: For Loop with ${array[@]}
Iterate over elements directly (preserves spaces in elements when quoted):
fruits=("apple" "banana split" "cherry")
for fruit in "${fruits[@]}"; do
echo "Current fruit: $fruit"
done
Output:
Current fruit: apple
Current fruit: banana split
Current fruit: cherry
Method 2: C-Style For Loop (Indices)
Loop over indices using a numeric range (avoids issues with sparse arrays):
for ((i=0; i<${#fruits[@]}; i++)); do
echo "Index $i: ${fruits[i]}"
done
Looping Through Associative Arrays
Loop Over Values
for city in "${capitals[@]}"; do
echo "Capital: $city"
done
Loop Over Keys
Use ${!array[@]} to get keys, then access values:
for country in "${!capitals[@]}"; do
echo "$country: ${capitals[$country]}"
done
Output:
France: Paris
Germany: Berlin (Capital)
Italy: Rome
Advanced Array Operations
Slicing
Extract a subset of elements with ${array[@]:start:count} (see Accessing Ranges).
Concatenation
Combine two arrays by expanding them into a new array:
arr1=("a" "b")
arr2=("c" "d")
combined=("${arr1[@]}" "${arr2[@]}") # combined=("a" "b" "c" "d")
Sorting
Use the sort command to sort array elements (works for indexed arrays):
numbers=(5 2 8 1 3)
sorted_numbers=($(printf "%s\n" "${numbers[@]}" | sort -n)) # -n for numeric sort
echo "${sorted_numbers[@]}" # Output: 1 2 3 5 8
Filtering
Use grep to filter elements matching a pattern:
words=("apple" "apricot" "banana" "avocado")
apple_related=($(printf "%s\n" "${words[@]}" | grep "^ap")) # Elements starting with "ap"
echo "${apple_related[@]}" # Output: apple apricot
Associative Arrays in Depth
Associative arrays shine for key-value storage. Here are advanced tips:
Check if a Key Exists
Use parameter expansion to test for keys:
if [[ -v capitals["France"] ]]; then
echo "France's capital is ${capitals["France"]}"
else
echo "France not found"
fi
(-v checks if the key is set; Bash 4.3+.)
Iterate Over Keys in Order
Associative array keys are unordered by default. To sort keys:
for country in $(printf "%s\n" "${!capitals[@]}" | sort); do
echo "$country: ${capitals[$country]}"
done
Practical Examples
Example 1: Command-Line Argument Processor
Use $@ (array of arguments) to handle inputs:
#!/bin/bash
args=("$@") # Store arguments in array
echo "You provided ${#args[@]} arguments:"
for i in "${!args[@]}"; do
echo "Arg $((i+1)): ${args[i]}"
done
Usage:
./script.sh hello world "bash arrays"
Output:
You provided 3 arguments:
Arg 1: hello
Arg 2: world
Arg 3: bash arrays
Example 2: Word Frequency Counter
Use an associative array to count word occurrences in a file:
#!/bin/bash
declare -A word_counts
# Read file line by line
while read -r line; do
# Split line into words (handle punctuation with tr)
words=($(echo "$line" | tr '[:upper:]' '[:lower:]' | tr -d '[:punct:]'))
for word in "${words[@]}"; do
((word_counts["$word"]++)) # Increment count for each word
done
done < "sample.txt"
# Print results sorted by frequency
echo "Word frequencies:"
for word in $(printf "%s\n" "${!word_counts[@]}" | sort); do
echo "$word: ${word_counts[$word]}"
done
Example 3: To-Do List Manager
Use an indexed array to manage tasks:
#!/bin/bash
declare -a todos=()
add_task() {
todos+=("$1")
echo "Added task: '$1' (Total: ${#todos[@]})"
}
list_tasks() {
echo -e "\nTo-Do List:"
for i in "${!todos[@]}"; do
echo "$((i+1)). ${todos[i]}"
done
}
# Usage: ./todo.sh add "Buy milk"
case "$1" in
add) add_task "$2" ;;
list) list_tasks ;;
*) echo "Usage: $0 {add 'task'|list}" ;;
esac
Common Pitfalls and Best Practices
1. Quoting ${array[@]}
Always quote ${array[@]} to preserve spaces in elements:
# Bad: Splits "hello world" into two elements
for item in ${words[@]}; do ... done
# Good: Preserves "hello world" as one element
for item in "${words[@]}"; do ... done
2. Use @ Instead of * in Loops
${array[*]} joins elements into a single string, which breaks iteration over elements with spaces. Use ${array[@]} instead.
3. Associative Arrays Require Bash 4+
macOS ships with Bash 3.2 (due to licensing). To use associative arrays, install Bash 4+ via Homebrew:
brew install bash # macOS
Then run scripts with bash script.sh (not sh).
4. Avoid Sparse Arrays Unless Necessary
Sparse arrays (e.g., arr[0]=a; arr[2]=b) can cause unexpected behavior in loops using for ((i=0; ...)). Use for i in "${!arr[@]}" to iterate over defined indices.
5. Unset vs. Clearing Arrays
unset arr deletes the array entirely, while arr=() clears elements but keeps the array defined.
Conclusion
Bash arrays are a versatile tool for organizing and processing data in shell scripts. From simple lists (indexed arrays) to key-value stores (associative arrays), they replace messy collections of individual variables and simplify iteration, filtering, and modification.
By mastering arrays, you’ll write cleaner, more efficient scripts that handle complex data with ease. Start small—replace space-separated strings with indexed arrays, then graduate to associative arrays for advanced use cases like counting or configuration management.
References
- GNU Bash Manual: Arrays
- Bash Hackers Wiki: Arrays
- Stack Overflow: Bash Arrays Guide
- Bash 4+ Installation for macOS
Let me know if you have any questions or need further clarification! Happy scripting! 🚀