Introduction to Bash Arrays
Arrays in Bash allow you to store multiple values in a single variable. They are essential for managing collections of data, processing lists of items, and creating more sophisticated scripts. Bash supports both indexed arrays (with numeric indices) and associative arrays (with string keys, in Bash 4+).
Key Concepts
- Indexed Arrays: Traditional arrays with numeric indices (starting at 0)
- Associative Arrays: Arrays with named keys (like dictionaries/maps)
- Dynamic Sizing: Arrays grow and shrink automatically
- Sparse Arrays: Indices don't need to be contiguous
- Reference vs Copy: Array variables hold references to data
1. Creating Arrays
Indexed Arrays
#!/bin/bash
# Method 1: Explicit declaration
declare -a fruits
fruits[0]="apple"
fruits[1]="banana"
fruits[2]="cherry"
# Method 2: Direct assignment
colors=("red" "green" "blue" "yellow")
# Method 3: Individual assignments
numbers[0]=10
numbers[1]=20
numbers[3]=40 # Sparse array (index 2 is empty)
# Method 4: Using compound assignment
files=($(ls)) # Creates array from command output
# Method 5: Reading from file
mapfile -t lines < file.txt # Read lines into array
# Method 6: Using eval (caution!)
eval "data=($(cat config.txt))"
# Method 7: Empty array
empty=()
# Method 8: Array with mixed types (everything is string)
mixed=(42 "hello" 3.14 "world")
# Examples
echo "Fruits: ${fruits[@]}"
echo "Colors: ${colors[@]}"
echo "Numbers: ${numbers[@]}"
Associative Arrays (Bash 4+)
#!/bin/bash # Declare associative array declare -A user # Add key-value pairs user[name]="John Doe" user[age]=30 user[city]="New York" user[email]="[email protected]" # Alternative declaration and initialization declare -A capitals=( ["USA"]="Washington" ["UK"]="London" ["France"]="Paris" ["Japan"]="Tokyo" ) # Initialize with values declare -A scores=( ["Alice"]=95 ["Bob"]=87 ["Charlie"]=92 ) # Examples echo "User name: ${user[name]}" echo "Capital of France: ${capitals[France]}" echo "Alice's score: ${scores[Alice]}"
2. Accessing Array Elements
Basic Access
#!/bin/bash
colors=("red" "green" "blue" "yellow")
# Access by index
echo "First color: ${colors[0]}" # red
echo "Second color: ${colors[1]}" # green
echo "Last color: ${colors[3]}" # yellow
# Access all elements
echo "All colors: ${colors[@]}" # red green blue yellow
echo "All colors: ${colors[*]}" # red green blue yellow
# Access with index variable
i=2
echo "Color at index $i: ${colors[$i]}" # blue
# Access last element (Bash 4.3+)
echo "Last element: ${colors[-1]}" # yellow
echo "Second last: ${colors[-2]}" # blue
# Access range of elements (Bash 4+)
echo "Elements 1-2: ${colors[@]:1:2}" # green blue
# Access from offset to end
echo "From index 2: ${colors[@]:2}" # blue yellow
# Associative array access
declare -A capitals=(["USA"]="Washington" ["UK"]="London")
echo "USA capital: ${capitals[USA]}"
echo "All capitals: ${capitals[@]}"
echo "All countries: ${!capitals[@]}"
Safe Access with Default Values
#!/bin/bash
# Using default value if index doesn't exist
colors=("red" "green" "blue")
# Safe access with default
echo "${colors[5]:-"unknown"}" # unknown (no error)
echo "${colors[1]:-"unknown"}" # green
# Using conditional
if [ -n "${colors[5]+x}" ]; then
echo "Element exists: ${colors[5]}"
else
echo "Element does not exist"
fi
# Check if index exists
if [ ${#colors[@]} -gt 5 ]; then
echo "Index 5 exists"
fi
# Get value or set default
value=${colors[5]:-"default"}
echo "Value: $value" # default
3. Array Operations
Getting Array Information
#!/bin/bash
colors=("red" "green" "blue" "yellow")
# Array length
echo "Number of elements: ${#colors[@]}" # 4
echo "Number of elements: ${#colors[*]}" # 4
# Length of specific element
echo "Length of first element: ${#colors[0]}" # 3 (length of "red")
# All indices
echo "Indices: ${!colors[@]}" # 0 1 2 3
# First element
echo "First element: ${colors[0]}"
# Last element
echo "Last element: ${colors[-1]}"
# Check if array is empty
if [ ${#colors[@]} -eq 0 ]; then
echo "Array is empty"
else
echo "Array has ${#colors[@]} elements"
fi
# Associative array keys
declare -A capitals=(["USA"]="DC" ["UK"]="London")
echo "Keys: ${!capitals[@]}" # USA UK
echo "Values: ${capitals[@]}" # DC London
Adding Elements
#!/bin/bash
# Initialize empty array
fruits=()
# Add elements one by one
fruits+=("apple")
fruits+=("banana")
fruits+=("cherry")
echo "Fruits: ${fruits[@]}" # apple banana cherry
# Add multiple elements
fruits+=("date" "elderberry" "fig")
echo "More fruits: ${fruits[@]}"
# Add at specific index
fruits[10]="kiwi" # Creates sparse array
echo "All fruits: ${fruits[@]}"
echo "Indices: ${!fruits[@]}"
# Append to existing element
fruits[0]="${fruits[0]}_suffix"
echo "Modified: ${fruits[0]}" # apple_suffix
# Add to front (shift all elements)
fruits=("new_front" "${fruits[@]}")
echo "With front: ${fruits[@]}"
# Insert at specific position
pos=3
fruits=("${fruits[@]:0:$pos}" "inserted" "${fruits[@]:$pos}")
echo "After insert: ${fruits[@]}"
# Add multiple elements from command
files=()
while IFS= read -r file; do
files+=("$file")
done < <(ls *.txt 2>/dev/null)
Removing Elements
#!/bin/bash
colors=("red" "green" "blue" "yellow" "purple" "orange")
# Remove by index
unset colors[1] # Removes "green"
echo "After unset index 1: ${colors[@]}"
echo "Indices: ${!colors[@]}" # 0 2 3 4 5
# Remove by value (find index first)
value="blue"
for i in "${!colors[@]}"; do
if [ "${colors[$i]}" = "$value" ]; then
unset colors[$i]
echo "Removed $value at index $i"
break
fi
done
# Remove multiple elements
unset colors[2] colors[3] # Remove specific indices
# Remove first element
colors=("${colors[@]:1}")
echo "After removing first: ${colors[@]}"
# Remove last element
colors=("${colors[@]:0:${#colors[@]}-1}")
echo "After removing last: ${colors[@]}"
# Remove range of elements
start=1
count=2
colors=("${colors[@]:0:$start}" "${colors[@]:$((start+count))}")
echo "After removing range: ${colors[@]}"
# Clear entire array
unset colors
echo "Array cleared: ${colors[@]:-}"
# Remove by pattern
numbers=(1 2 3 4 5 6 7 8 9 10)
for i in "${!numbers[@]}"; do
if (( numbers[i] % 2 == 0 )); then
unset numbers[i]
fi
done
echo "Odd numbers: ${numbers[@]}" # 1 3 5 7 9
Modifying Elements
#!/bin/bash
words=("hello" "world" "bash" "scripting")
# Update by index
words[1]="universe"
echo "After update: ${words[@]}" # hello universe bash scripting
# Apply transformation to all elements
for i in "${!words[@]}"; do
words[$i]="${words[$i]^^}" # Uppercase
done
echo "Uppercase: ${words[@]}" # HELLO UNIVERSE BASH SCRIPTING
# Replace substring in all elements
for i in "${!words[@]}"; do
words[$i]="${words[$i]/HELLO/HI}"
done
echo "After replacement: ${words[@]}" # HI UNIVERSE BASH SCRIPTING
# Append to each element
for i in "${!words[@]}"; do
words[$i]="${words[$i]}_suffix"
done
echo "With suffix: ${words[@]}"
# Conditional modification
numbers=(1 2 3 4 5 6 7 8 9 10)
for i in "${!numbers[@]}"; do
if (( numbers[i] % 2 == 0 )); then
numbers[i]=$((numbers[i] * 2))
fi
done
echo "Modified numbers: ${numbers[@]}"
4. Iterating Over Arrays
Basic Loops
#!/bin/bash
fruits=("apple" "banana" "cherry" "date")
# Loop over elements (for loop)
echo "Fruits:"
for fruit in "${fruits[@]}"; do
echo " - $fruit"
done
# Loop over indices
echo -e "\nFruits with indices:"
for i in "${!fruits[@]}"; do
echo " [$i] = ${fruits[$i]}"
done
# C-style for loop
echo -e "\nC-style loop:"
for (( i=0; i<${#fruits[@]}; i++ )); do
echo " fruits[$i] = ${fruits[$i]}"
done
# While loop with index
echo -e "\nWhile loop:"
i=0
while [ $i -lt ${#fruits[@]} ]; do
echo " fruits[$i] = ${fruits[$i]}"
((i++))
done
# Until loop
echo -e "\nUntil loop:"
i=0
until [ $i -ge ${#fruits[@]} ]; do
echo " fruits[$i] = ${fruits[$i]}"
((i++))
done
Advanced Iteration
#!/bin/bash
# Loop over sparse array
sparse=([0]="first" [2]="second" [5]="third")
for i in "${!sparse[@]}"; do
echo "Index $i: ${sparse[$i]}"
done
# Loop with enumeration
colors=("red" "green" "blue" "yellow")
for i in "${!colors[@]}"; do
printf "%2d: %s\n" "$i" "${colors[$i]}"
done
# Process in chunks
numbers=({1..20})
chunk_size=5
for (( i=0; i<${#numbers[@]}; i+=chunk_size )); do
chunk=("${numbers[@]:i:chunk_size}")
echo "Processing chunk starting at $i: ${chunk[@]}"
done
# Reverse iteration
for (( i=${#colors[@]}-1; i>=0; i-- )); do
echo "colors[$i] = ${colors[$i]}"
done
# Nested loops (2D array simulation)
matrix=(
"1 2 3"
"4 5 6"
"7 8 9"
)
for row in "${matrix[@]}"; do
for val in $row; do
echo -n "$val "
done
echo
done
Functional Programming Style
#!/bin/bash
# Map (transform each element)
map() {
local arr=("${!1}")
local result=()
for item in "${arr[@]}"; do
result+=($($2 "$item"))
done
echo "${result[@]}"
}
# Filter elements
filter() {
local arr=("${!1}")
local result=()
for item in "${arr[@]}"; do
if $2 "$item"; then
result+=("$item")
fi
done
echo "${result[@]}"
}
# Reduce (fold)
reduce() {
local arr=("${!1}")
local acc=$2
local op=$3
for item in "${arr[@]}"; do
acc=$((acc $op item))
done
echo $acc
}
# Usage
nums=(1 2 3 4 5)
# Square function
square() { echo $(( $1 * $1 )); }
# Is even function
is_even() { (( $1 % 2 == 0 )); }
squared=$(map nums[@] square)
echo "Squared: $squared"
evens=$(filter nums[@] is_even)
echo "Evens: $evens"
sum=$(reduce nums[@] 0 +)
echo "Sum: $sum"
5. Array Manipulation Techniques
Sorting Arrays
#!/bin/bash
# Sort numerically
numbers=(5 2 8 1 9 3 7 4 6)
sorted=($(for n in "${numbers[@]}"; do echo $n; done | sort -n))
echo "Sorted numbers: ${sorted[@]}"
# Sort reverse numerically
sorted=($(for n in "${numbers[@]}"; do echo $n; done | sort -nr))
echo "Reverse sorted: ${sorted[@]}"
# Sort alphabetically
fruits=("banana" "apple" "cherry" "date")
sorted=($(for f in "${fruits[@]}"; do echo $f; done | sort))
echo "Sorted fruits: ${sorted[@]}"
# Sort by string length
sorted=($(for f in "${fruits[@]}"; do echo "$f"; done | awk '{ print length, $0 }' | sort -n | cut -d' ' -f2-))
echo "Sorted by length: ${sorted[@]}"
# Sort with custom comparator
sort_by_last_char() {
for item in "$@"; do
echo "${item: -1}:$item"
done | sort | cut -d: -f2-
}
words=("hello" "world" "bash" "scripting")
sorted=($(sort_by_last_char "${words[@]}"))
echo "Sorted by last char: ${sorted[@]}"
Unique Elements
#!/bin/bash
# Get unique elements
numbers=(1 2 2 3 3 3 4 4 4 4 5)
unique=($(printf "%s\n" "${numbers[@]}" | sort -u))
echo "Unique: ${unique[@]}"
# Count occurrences
declare -A count
for num in "${numbers[@]}"; do
((count[$num]++))
done
for num in "${!count[@]}"; do
echo "$num appears ${count[$num]} times"
done
# Remove duplicates (preserve order)
unique=()
for item in "${numbers[@]}"; do
found=0
for u in "${unique[@]}"; do
if [ "$u" = "$item" ]; then
found=1
break
fi
done
if [ $found -eq 0 ]; then
unique+=("$item")
fi
done
echo "Unique (order preserved): ${unique[@]}"
Array Slicing and Joining
#!/bin/bash
colors=("red" "green" "blue" "yellow" "purple" "orange")
# Slice array
slice=("${colors[@]:1:3}") # Elements 1-3
echo "Slice (1-3): ${slice[@]}" # green blue yellow
# All elements from index 2
from_index2=("${colors[@]:2}")
echo "From index 2: ${from_index2[@]}" # blue yellow purple orange
# First 3 elements
first3=("${colors[@]::3}")
echo "First 3: ${first3[@]}" # red green blue
# Last 3 elements
last3=("${colors[@]: -3}")
echo "Last 3: ${last3[@]}" # yellow purple orange
# Join array elements with delimiter
join() {
local IFS="$1"
shift
echo "$*"
}
joined=$(join , "${colors[@]}")
echo "Joined with comma: $joined" # red,green,blue,yellow,purple,orange
joined=$(join " | " "${colors[@]}")
echo "Joined with pipe: $joined" # red | green | blue | yellow | purple | orange
# Split string into array
string="apple,banana,cherry"
IFS=',' read -ra fruits <<< "$string"
echo "Split array: ${fruits[@]}"
Array Reversal and Rotation
#!/bin/bash
# Reverse array
reverse() {
local arr=("${!1}")
local result=()
for (( i=${#arr[@]}-1; i>=0; i-- )); do
result+=("${arr[$i]}")
done
echo "${result[@]}"
}
colors=("red" "green" "blue" "yellow")
reversed=($(reverse colors[@]))
echo "Reversed: ${reversed[@]}" # yellow blue green red
# Rotate left
rotate_left() {
local arr=("${!1}")
local n=$2
n=$((n % ${#arr[@]}))
echo "${arr[@]:n}" "${arr[@]:0:n}"
}
rotated=($(rotate_left colors[@] 2))
echo "Rotated left by 2: ${rotated[@]}" # blue yellow red green
# Rotate right
rotate_right() {
local arr=("${!1}")
local n=$2
n=$((n % ${#arr[@]}))
echo "${arr[@]: -n}" "${arr[@]:0:${#arr[@]}-n}"
}
rotated=($(rotate_right colors[@] 2))
echo "Rotated right by 2: ${rotated[@]}" # green blue red yellow
# Shuffle array
shuffle() {
local arr=("${!1}")
local result=()
local indices=(${!arr[@]})
while [ ${#indices[@]} -gt 0 ]; do
local rand=$((RANDOM % ${#indices[@]}))
local idx=${indices[$rand]}
result+=("${arr[$idx]}")
unset indices[$rand]
indices=("${indices[@]}")
done
echo "${result[@]}"
}
shuffled=($(shuffle colors[@]))
echo "Shuffled: ${shuffled[@]}"
6. Multidimensional Arrays
Simulating 2D Arrays
#!/bin/bash
# Method 1: Using arrays of strings
matrix=(
"1 2 3"
"4 5 6"
"7 8 9"
)
# Access element
get_element() {
local row=$1
local col=$2
local line="${matrix[$row]}"
local elements=($line)
echo "${elements[$col]}"
}
echo "Element at (1,1): $(get_element 1 1)" # 5
# Print matrix
print_matrix() {
for row in "${matrix[@]}"; do
echo "$row"
done
}
echo -e "\nMatrix:"
print_matrix
# Method 2: Using associative arrays
declare -A matrix2
matrix2[0,0]=1
matrix2[0,1]=2
matrix2[0,2]=3
matrix2[1,0]=4
matrix2[1,1]=5
matrix2[1,2]=6
matrix2[2,0]=7
matrix2[2,1]=8
matrix2[2,2]=9
echo -e "\nMatrix2[1,1] = ${matrix2[1,1]}" # 5
# Method 3: Using arrays of arrays (Bash 4+)
declare -A matrix3
matrix3["row0"]="1 2 3"
matrix3["row1"]="4 5 6"
matrix3["row2"]="7 8 9"
# Method 4: Using eval for dynamic access
rows=3
cols=3
declare -A matrix4
for (( i=0; i<rows; i++ )); do
for (( j=0; j<cols; j++ )); do
matrix4[$i,$j]=$((i*cols + j + 1))
done
done
echo "Matrix4[2,2] = ${matrix4[2,2]}" # 9
Matrix Operations
#!/bin/bash
# Matrix addition
matrix_add() {
local rows=$1
local cols=$2
shift 2
local -a a=("${!1}")
local -a b=("${!2}")
local -a result=()
for (( i=0; i<rows; i++ )); do
for (( j=0; j<cols; j++ )); do
idx=$((i*cols + j))
result[idx]=$((a[idx] + b[idx]))
done
done
echo "${result[@]}"
}
# Matrix multiplication
matrix_multiply() {
local rows1=$1
local cols1=$2
local rows2=$3
local cols2=$4
shift 4
local -a a=("${!1}")
local -a b=("${!2}")
local -a result=()
if [ $cols1 -ne $rows2 ]; then
echo "Incompatible dimensions"
return 1
fi
for (( i=0; i<rows1; i++ )); do
for (( j=0; j<cols2; j++ )); do
sum=0
for (( k=0; k<cols1; k++ )); do
sum=$((sum + a[i*cols1 + k] * b[k*cols2 + j]))
done
result[i*cols2 + j]=$sum
done
done
echo "${result[@]}"
}
# Example usage
a=(1 2 3 4) # 2x2 matrix: [1 2; 3 4]
b=(5 6 7 8) # 2x2 matrix: [5 6; 7 8]
result=($(matrix_add 2 2 a[@] b[@]))
echo "Matrix addition: ${result[@]}" # [6 8; 10 12]
result=($(matrix_multiply 2 2 2 2 a[@] b[@]))
echo "Matrix multiplication: ${result[@]}" # [19 22; 43 50]
7. Arrays in Functions
Passing Arrays to Functions
#!/bin/bash
# Arrays can't be passed directly - pass by name
print_array() {
local -n arr=$1 # Nameref (Bash 4.3+)
echo "Array contents: ${arr[@]}"
}
# Alternative: pass by value (requires copying)
process_array() {
local arr=("${!1}")
echo "Processing: ${arr[@]}"
arr[0]="modified"
echo "Modified: ${arr[@]}"
}
# Return array from function
get_numbers() {
local result=(1 2 3 4 5)
echo "${result[@]}" # Return as string
}
# Usage
my_array=("apple" "banana" "cherry")
# Pass by name (modifiable)
print_array my_array
# Pass by value (copy)
process_array my_array[@] # Original unchanged
echo "Original: ${my_array[@]}" # Still "apple banana cherry"
# Capture returned array
numbers=($(get_numbers))
echo "Numbers: ${numbers[@]}"
Array Utility Functions
#!/bin/bash
# Check if array contains element
contains() {
local -n arr=$1
local value=$2
for item in "${arr[@]}"; do
if [ "$item" = "$value" ]; then
return 0
fi
done
return 1
}
# Get index of element
index_of() {
local -n arr=$1
local value=$2
for i in "${!arr[@]}"; do
if [ "${arr[$i]}" = "$value" ]; then
echo $i
return 0
fi
done
echo -1
return 1
}
# Remove element by value
remove() {
local -n arr=$1
local value=$2
local new_arr=()
for item in "${arr[@]}"; do
if [ "$item" != "$value" ]; then
new_arr+=("$item")
fi
done
arr=("${new_arr[@]}")
}
# Union of two arrays
array_union() {
local -n arr1=$1
local -n arr2=$2
local result=()
local seen=()
for item in "${arr1[@]}" "${arr2[@]}"; do
if [[ ! " ${seen[@]} " =~ " ${item} " ]]; then
result+=("$item")
seen+=("$item")
fi
done
echo "${result[@]}"
}
# Intersection of two arrays
array_intersection() {
local -n arr1=$1
local -n arr2=$2
local result=()
for item1 in "${arr1[@]}"; do
for item2 in "${arr2[@]}"; do
if [ "$item1" = "$item2" ]; then
result+=("$item1")
break
fi
done
done
echo "${result[@]}"
}
# Usage examples
fruits=("apple" "banana" "cherry" "date")
if contains fruits "banana"; then
echo "Contains banana"
fi
idx=$(index_of fruits "cherry")
echo "Index of cherry: $idx"
remove fruits "banana"
echo "After removal: ${fruits[@]}"
a=(1 2 3 4)
b=(3 4 5 6)
union=($(array_union a b))
echo "Union: ${union[@]}"
intersection=($(array_intersection a b))
echo "Intersection: ${intersection[@]}"
8. Practical Examples
CSV Parser
#!/bin/bash
# Parse CSV file into array of records
parse_csv() {
local file=$1
local -n data=$2
local line_num=0
while IFS= read -r line; do
# Skip empty lines
[ -z "$line" ] && continue
# Parse CSV (simple version - doesn't handle quoted commas)
IFS=',' read -ra fields <<< "$line"
# Store as "line_num:field_num=value"
for i in "${!fields[@]}"; do
data["$line_num,$i"]="${fields[$i]}"
done
((line_num++))
done < "$file"
echo "Parsed $line_num lines"
}
# Display CSV data
display_csv() {
local -n data=$1
local max_cols=0
# Find max columns
for key in "${!data[@]}"; do
col="${key#*,}"
[ "$col" -gt "$max_cols" ] && max_cols=$col
done
# Print header
for (( c=0; c<=max_cols; c++ )); do
printf "%-15s" "Column $c"
done
echo
# Print data
local row=0
while true; do
found=0
line=""
for (( c=0; c<=max_cols; c++ )); do
value="${data[$row,$c]}"
if [ -n "$value" ]; then
found=1
printf "%-15s" "$value"
else
printf "%-15s" ""
fi
done
echo
[ $found -eq 0 ] && break
((row++))
done
}
# Create sample CSV
cat > sample.csv << EOF
Name,Age,City
John,30,New York
Jane,25,Los Angeles
Bob,35,Chicago
EOF
# Parse and display
declare -A csv_data
parse_csv sample.csv csv_data
display_csv csv_data
rm sample.csv
Configuration File Parser
#!/bin/bash
# Parse config file into associative array
parse_config() {
local file=$1
local -n config=$2
while IFS= read -r line; do
# Skip comments and empty lines
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[ -z "$line" ] && continue
# Parse key=value
if [[ "$line" =~ ^[[:space:]]*([^=]+)[[:space:]]*=[[:space:]]*(.*)$ ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
# Remove trailing comments
value="${value%%#*}"
# Trim whitespace
key=$(echo "$key" | xargs)
value=$(echo "$value" | xargs)
config["$key"]="$value"
fi
done < "$file"
}
# Save config to file
save_config() {
local -n config=$1
local file=$2
{
echo "# Configuration file generated $(date)"
for key in "${!config[@]}"; do
echo "$key = ${config[$key]}"
done
} > "$file"
}
# Create sample config
cat > app.conf << EOF
# Application configuration
database_host = localhost
database_port = 5432
app_name = MyApp
debug_mode = true
max_users = 100
EOF
# Parse config
declare -A config
parse_config app.conf config
# Display config
echo "Configuration:"
for key in "${!config[@]}"; do
echo " $key = ${config[$key]}"
done
# Modify and save
config["debug_mode"]="false"
config["max_users"]=200
save_config config app_new.conf
echo -e "\nUpdated config saved to app_new.conf"
Directory Tree Scanner
#!/bin/bash
# Scan directory recursively into arrays
declare -a files
declare -a dirs
declare -A file_sizes
scan_directory() {
local dir=$1
local prefix=$2
for item in "$dir"/*; do
[ -e "$item" ] || continue
if [ -f "$item" ]; then
files+=("$prefix$(basename "$item")")
file_sizes["$prefix$(basename "$item")"]=$(stat -c%s "$item" 2>/dev/null || stat -f%z "$item" 2>/dev/null)
elif [ -d "$item" ]; then
local name=$(basename "$item")
dirs+=("$prefix$name")
scan_directory "$item" "$prefix$name/"
fi
done
}
# Display tree
display_tree() {
echo "Directories:"
printf ' %s\n' "${dirs[@]}"
echo -e "\nFiles:"
for file in "${files[@]}"; do
size=${file_sizes[$file]}
printf " %s (%d bytes)\n" "$file" "$size"
done
echo -e "\nStatistics:"
echo " Total directories: ${#dirs[@]}"
echo " Total files: ${#files[@]}"
total_size=0
for size in "${file_sizes[@]}"; do
total_size=$((total_size + size))
done
echo " Total size: $total_size bytes"
}
# Scan current directory
scan_directory "." ""
display_tree
Command History Manager
#!/bin/bash
# Simple command history manager
declare -a history
declare -A command_count
# Add command to history
add_command() {
local cmd=$1
history+=("$(date '+%Y-%m-%d %H:%M:%S'): $cmd")
((command_count["$cmd"]++))
}
# Show recent commands
recent() {
local n=${1:-10}
local start=$(( ${#history[@]} > n ? ${#history[@]} - n : 0 ))
echo "Recent commands:"
for (( i=start; i<${#history[@]}; i++ )); do
echo " $((i+1)). ${history[$i]}"
done
}
# Show command statistics
stats() {
echo "Command statistics:"
for cmd in "${!command_count[@]}"; do
echo " $cmd: ${command_count[$cmd]} times"
done | sort -rn -k3
}
# Search history
search_history() {
local pattern=$1
echo "Search results for '$pattern':"
for entry in "${history[@]}"; do
if [[ "$entry" =~ $pattern ]]; then
echo " $entry"
fi
done
}
# Example usage
add_command "ls -la"
add_command "cd /tmp"
add_command "grep pattern file.txt"
add_command "ls -la"
add_command "ps aux"
recent
echo -e "\n"
stats
echo -e "\n"
search_history "ls"
9. Advanced Array Techniques
Using Arrays with Process Substitution
#!/bin/bash
# Read command output directly into array
mapfile -t users < <(cut -d: -f1 /etc/passwd | head -5)
echo "First 5 users: ${users[@]}"
# Process array with while loop
printf "%s\n" "${users[@]}" | while read user; do
echo "Processing user: $user"
done
# Use process substitution with arrays
diff <(printf "%s\n" "${array1[@]}" | sort) <(printf "%s\n" "${array2[@]}" | sort)
# Create array from find results
mapfile -t txt_files < <(find . -name "*.txt" -type f)
echo "Found ${#txt_files[@]} text files"
# Parallel processing with arrays
process_chunk() {
local chunk=("${!1}")
for item in "${chunk[@]}"; do
echo "Processing: $item"
# Do work here
done
}
# Split array into chunks and process in parallel
chunk_size=10
for (( i=0; i<${#files[@]}; i+=chunk_size )); do
chunk=("${files[@]:i:chunk_size}")
process_chunk chunk[@] &
done
wait
Array Index Manipulation
#!/bin/bash
# Reindex array (remove gaps)
reindex() {
local -n arr=$1
arr=("${arr[@]}")
}
# Shift indices
shift_indices() {
local -n arr=$1
local shift=$2
local new_arr=()
for i in "${!arr[@]}"; do
new_arr[$((i + shift))]="${arr[$i]}"
done
arr=("${new_arr[@]}")
}
# Compact array (remove empty elements)
compact() {
local -n arr=$1
local new_arr=()
for item in "${arr[@]}"; do
[ -n "$item" ] && new_arr+=("$item")
done
arr=("${new_arr[@]}")
}
# Example
sparse=( [0]="a" [2]="b" [5]="c" )
echo "Sparse: ${sparse[@]}"
echo "Indices: ${!sparse[@]}"
reindex sparse
echo "Reindexed: ${sparse[@]}"
echo "Indices: ${!sparse[@]}"
shift_indices sparse 2
echo "Shifted: ${sparse[@]}"
echo "Indices: ${!sparse[@]}"
mixed=("a" "" "b" "" "c" "")
compact mixed
echo "Compacted: ${mixed[@]}"
Array References (Bash 4.3+)
#!/bin/bash
# Using namerefs for array references
process_arrays() {
local -n arr1=$1
local -n arr2=$2
echo "Array1: ${arr1[@]}"
echo "Array2: ${arr2[@]}"
# Modify through reference
arr1[0]="modified"
}
# Array of array names
arrays=(fruits colors numbers)
# Declare some arrays
fruits=("apple" "banana" "cherry")
colors=("red" "green" "blue")
numbers=(1 2 3 4 5)
# Process each array
for arr_name in "${arrays[@]}"; do
local -n arr=$arr_name
echo "Processing $arr_name: ${arr[@]}"
done
# Call function with references
process_arrays fruits colors
echo "Fruits after: ${fruits[@]}" # First element modified
10. Best Practices and Tips
Common Patterns
#!/bin/bash
# 1. Always quote array expansions
fruits=("apple" "banana with space" "cherry")
# Good - preserves elements with spaces
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
# Bad - splits on spaces
for fruit in ${fruits[@]}; do
echo "Fruit: $fruit" # "banana" and "with" and "space" become separate
done
# 2. Check if array is empty
if [ ${#array[@]} -eq 0 ]; then
echo "Array is empty"
fi
# 3. Default array
empty_array=()
default_array=(${empty_array[@]:-${default_values[@]}})
# 4. Safe array copying
copy_array() {
local -n src=$1
local -n dst=$2
dst=("${src[@]}")
}
# 5. Array intersection using associative array
array_intersect() {
local -n arr1=$1
local -n arr2=$2
local -A temp
local result=()
for item in "${arr2[@]}"; do
temp["$item"]=1
done
for item in "${arr1[@]}"; do
[ -n "${temp[$item]}" ] && result+=("$item")
done
echo "${result[@]}"
}
Performance Considerations
#!/bin/bash
# Large array operations can be slow
# Use associative arrays for lookups instead of linear search
# Bad - linear search O(n)
slow_lookup() {
local -n arr=$1
local value=$2
for item in "${arr[@]}"; do
[ "$item" = "$value" ] && return 0
done
return 1
}
# Good - hash lookup O(1)
declare -A fast_lookup
for item in "${large_array[@]}"; do
fast_lookup["$item"]=1
done
if [ -n "${fast_lookup[$value]}" ]; then
echo "Found"
fi
# Use mapfile for reading files instead of while-read
# Slow
while read line; do
arr+=("$line")
done < file.txt
# Fast
mapfile -t arr < file.txt
# Use local variables in functions for speed
slow_function() {
arr=("${!1}")
}
fast_function() {
local -n arr=$1 # Reference is faster than copy
}
Debugging Arrays
#!/bin/bash
# Print array with indices
print_array() {
local -n arr=$1
echo "Array: $1"
for i in "${!arr[@]}"; do
echo " [$i] = '${arr[$i]}'"
done
}
# Debug function
debug_array() {
if [ "${DEBUG:-0}" -eq 1 ]; then
local -n arr=$1
echo "DEBUG: $1 (size=${#arr[@]})"
for i in "${!arr[@]}"; do
echo " [$i] = '${arr[$i]}'"
done
fi
}
# Validate array indices
validate_indices() {
local -n arr=$1
local expected=$2
for (( i=0; i<expected; i++ )); do
if [ -z "${arr[$i]+x}" ]; then
echo "Warning: Missing index $i"
fi
done
}
# Example
DEBUG=1
my_array=("a" "b" "c")
debug_array my_array
11. Command Summary and Cheat Sheet
Quick Reference
# Declaration
declare -a arr # Indexed array
declare -A assoc # Associative array
arr=() # Empty array
arr=(1 2 3) # Initialize with values
assoc=([key1]=val1 [key2]=val2) # Initialize associative
# Access
${arr[@]} # All values
${!arr[@]} # All indices
${#arr[@]} # Array length
${#arr[0]} # Length of element 0
${arr[i]} # Element at index i
${arr[-1]} # Last element
# Slicing
${arr[@]:start:count} # Slice array
${arr[@]:start} # From start to end
${arr[@]::count} # First count elements
${arr[@]: -count} # Last count elements
# Modification
arr+=("new") # Append element
arr=("new" "${arr[@]}") # Prepend element
arr[i]="value" # Set element
unset arr[i] # Remove element
arr=("${arr[@]}") # Reindex array
# Operations
for item in "${arr[@]}"; do # Iterate values
for i in "${!arr[@]}"; do # Iterate indices
printf "%s\n" "${arr[@]}" # Print each on new line
# Copying
new=("${old[@]}") # Copy array
declare -n ref=arr # Create reference
# Reading
mapfile -t arr < file # Read file into array
IFS=',' read -ra arr <<< "a,b,c" # Split string
# Testing
[ ${#arr[@]} -eq 0 ] # Check if empty
[[ " ${arr[@]} " =~ " $value " ]] # Check if contains
Conclusion
Key Takeaways
- Array Types: Indexed (numeric) and Associative (string keys)
- Dynamic: Arrays grow and shrink automatically
- Sparse: Indices don't need to be contiguous
- Passing: Arrays are passed by name, not by value
- Operations: Rich set of operations for manipulation
Best Practices Summary
| Operation | Recommended Method |
|---|---|
| Declaration | declare -a arr or arr=() |
| Iteration | for item in "${arr[@]}" |
| Length | ${#arr[@]} |
| Append | arr+=("new") |
| Remove | unset arr[i] |
| Copy | new=("${old[@]}") |
| Contains | Use associative array for lookups |
| Read file | mapfile -t arr < file |
| Pass to function | Use nameref (local -n) |
Common Pitfalls
# Pitfall 1: Not quoting array expansion
for fruit in ${fruits[@]} # Wrong - splits on spaces
for fruit in "${fruits[@]}" # Correct
# Pitfall 2: Using [*] instead of [@]
echo "${fruits[*]}" # All elements as one string
echo "${fruits[@]}" # Separate elements
# Pitfall 3: Forgetting indices are zero-based
fruits[1]="apple" # This is actually the second element
# Pitfall 4: Not checking if index exists
value=${fruits[100]} # Returns empty string, no error
# Pitfall 5: Modifying IFS incorrectly
IFS=',' read -ra arr <<< "a,b,c" # Works
# Remember to restore IFS or use local
# Pitfall 6: Using array in scalar context
echo $fruits # Only prints first element
echo ${fruits[@]} # Correct - prints all
# Pitfall 7: Array comparison
[ "$arr1" = "$arr2" ] # Wrong - compares as strings
# Compare elements manually or use diff
Mastering Bash arrays is essential for writing efficient and powerful shell scripts. They provide a flexible way to manage collections of data and are fundamental to many advanced scripting techniques.