14.1. Arrays#

14.1.1. Advanced Array Patterns#

14.1.1.1. Array Functions#

#!/bin/bash

# Check if element in array
array_contains() {
  local target=$1
  shift
  local -a arr=("$@")
  
  for item in "${arr[@]}"; do
    [[ "$item" == "$target" ]] && return 0
  done
  return 1
}

if array_contains "cherry" apple banana cherry date; then
  echo "Found cherry"
fi

# Remove element from array
array_remove() {
  local target=$1
  shift
  local -a arr=("$@")
  local -a result=()
  
  for item in "${arr[@]}"; do
    [[ "$item" != "$target" ]] && result+=("$item")
  done
  
  echo "${result[@]}"
}

new_arr=($(array_remove "banana" apple banana cherry))

# Sort array
array_sort() {
  local -a arr=("$@")
  printf '%s\n' "${arr[@]}" | sort
}

sorted=($(array_sort 3 1 4 1 5 9 2 6))

# Unique elements
array_unique() {
  local -a arr=("$@")
  printf '%s\n' "${arr[@]}" | sort -u
}

unique=($(array_unique 3 1 4 1 5 9 2 6))

14.1.1.2. Working with Multidimensional Data#

#!/bin/bash

# Simulate 2D array (using special delimiter)
declare -A matrix
set_cell() {
  local row=$1 col=$2 value=$3
  matrix["$row:$col"]=$value
}

get_cell() {
  local row=$1 col=$2
  echo ${matrix["$row:$col"]}
}

# Create 3x3 grid
for ((i=0; i<3; i++)); do
  for ((j=0; j<3; j++)); do
    set_cell $i $j "[$i,$j]"
  done
done

# Print matrix
print_matrix() {
  for ((i=0; i<3; i++)); do
    for ((j=0; j<3; j++)); do
      printf "%8s " "$(get_cell $i $j)"
    done
    echo
  done
}

print_matrix

# List data (array of associative arrays)
declare -a users=(
  "[name]=Alice [age]=25 [city]=NYC"
  "[name]=Bob [age]=30 [city]=LA"
  "[name]=Charlie [age]=35 [city]=SF"
)

# Parse and use
for user_str in "${users[@]}"; do
  declare -A user
  eval "$user_str"
  
  echo "${user[name]} is ${user[age]} years old from ${user[city]}"
  
  unset user
done

14.1.2. Associative Arrays (Hash Maps)#

Associative arrays map keys to values, enabling sophisticated data structures.

14.1.2.1. Basic Associative Arrays#

#!/bin/bash

# Declare associative array
declare -A config
config[host]="localhost"
config[port]=8080
config[debug]="true"

# Initialize with values
declare -A user=(
  [name]="John"
  [age]=30
  [email]="john@example.com"
)

# Add elements
config[timeout]=5000

# Access elements
echo ${config[host]}    # localhost
echo ${config[port]}    # 8080

# All keys
echo ${!config[@]}      # host port debug timeout

# All values
echo ${config[@]}       # localhost 8080 true 5000

# Check if key exists
if [[ -v config[host] ]]; then
  echo "host key exists"
fi

14.1.2.2. Iterating Associative Arrays#

#!/bin/bash

declare -A servers=(
  [web1]="192.168.1.10"
  [web2]="192.168.1.11"
  [db1]="192.168.1.20"
  [db2]="192.168.1.21"
)

# Iterate over keys
for server in "${!servers[@]}"; do
  echo "$server: ${servers[$server]}"
done

# Iterate with sort (keys)
for server in $(echo "${!servers[@]}" | tr ' ' '\n' | sort); do
  echo "$server: ${servers[$server]}"
done

# Iterate over values
for ip in "${servers[@]}"; do
  ping -c 1 "$ip"
done

# Find key by value
find_key_by_value() {
  local target=$1
  for key in "${!servers[@]}"; do
    if [[ "${servers[$key]}" == "$target" ]]; then
      echo "$key"
      return 0
    fi
  done
  return 1
}

server_name=$(find_key_by_value "192.168.1.10")
echo "IP 192.168.1.10 belongs to: $server_name"

14.1.2.3. Practical Use Cases#

#!/bin/bash

# Configuration management
declare -A db_config=(
  [host]="localhost"
  [port]="5432"
  [user]="postgres"
  [password]="secret"
)

# Function to get config with default
get_config() {
  local key=$1
  local default=${2:-""}
  
  if [[ -v db_config[$key] ]]; then
    echo "${db_config[$key]}"
  else
    echo "$default"
  fi
}

# Count occurrences
count_items() {
  local -a items=("$@")
  declare -A counts
  
  for item in "${items[@]}"; do
    ((counts[$item]++))
  done
  
  for item in "${!counts[@]}"; do
    echo "$item: ${counts[$item]}"
  done
}

count_items apple banana apple cherry banana apple
# Output:
# apple: 3
# banana: 2
# cherry: 1

# Group by attribute
declare -A groups
while IFS=":" read -r name group; do
  [[ "$name" == "#"* ]] && continue  # Skip comments
  groups[$group]+="$name "
done < /etc/group

# Print groups
for group_name in "${!groups[@]}"; do
  echo "$group_name: ${groups[$group_name]}"
done

14.1.2.4. Common Pitfalls#

#!/bin/bash

# Pitfall 1: Not declaring as associative array
bad_array[key]="value"  # Treats as indexed array, key becomes 0
declare -A good_array
good_array[key]="value"  # Correctly uses key

# Pitfall 2: Forgetting quotes with @ or *
declare -A arr=(["a"]="1" ["b"]="2")
for key in ${!arr[@]}; do  # WRONG: expands to "a b"
  echo "$key"
done
# Should be: for key in "${!arr[@]}"; do

# Pitfall 3: Keys with spaces
config["log level"]="debug"  # Works, but key has space
config["log_level"]="debug"  # Better: use underscore

# Pitfall 4: Unset elements
unset arr[key]
echo ${arr[key]}  # Empty, but no error

# Check with -v
if [[ -v arr[key] ]]; then
  echo "Key exists"
else
  echo "Key doesn't exist"
fi

14.1.3. Working with Indexed Arrays#

Indexed arrays store multiple values with numeric indices, fundamental for data processing.

14.1.3.1. Array Basics#

#!/bin/bash

# Create array with explicit indices
declare -a fruits=("apple" "banana" "cherry")

# Implicit indices (0-based)
colors=(red green blue yellow)

# Add to array
colors+=(orange purple)  # Append multiple

# Array with gaps in indices
declare -a sparse
sparse[0]="first"
sparse[5]="sixth"  # Creates sparse array
sparse[2]="third"

# Create array from command output
files=($(ls *.txt))
lines=($(cat file.txt))

# Create from variable expansion
numbers=({1..10})  # Brace expansion
letters=(a b c d e)

14.1.3.2. Accessing Array Elements#

#!/bin/bash

arr=(apple banana cherry date elderberry)

# Access by index
echo ${arr[0]}      # apple
echo ${arr[2]}      # cherry
echo ${arr[-1]}     # elderberry (Bash 4.3+, negative indexing)

# All elements
echo ${arr[@]}      # apple banana cherry date elderberry
echo ${arr[*]}      # Same, but as single word if unquoted

# Length of array
echo ${#arr[@]}     # 5
echo ${#arr[*]}     # 5

# Indices
echo ${!arr[@]}     # 0 1 2 3 4

# Substring of element
echo ${arr[0]:0:3}  # app
echo ${arr[0]:1:3}  # ppl

# Array slice (Bash 4.3+)
echo "${arr[@]:1:3}"  # banana cherry date

14.1.3.3. Iterating Arrays#

#!/bin/bash

arr=(apple banana cherry)

# Loop with index
for i in "${!arr[@]}"; do
  echo "$i: ${arr[$i]}"
done
# Output:
# 0: apple
# 1: banana
# 2: cherry

# Loop over elements
for fruit in "${arr[@]}"; do
  echo "Fruit: $fruit"
done

# While loop with index
i=0
while [[ $i -lt ${#arr[@]} ]]; do
  echo "${arr[$i]}"
  ((i++))
done

# Conditional iteration
for item in "${arr[@]}"; do
  if [[ "$item" == "banana" ]]; then
    echo "Found banana at index $i"
  fi
done

14.1.3.4. Array Operations#

#!/bin/bash

arr=(3 1 4 1 5 9 2 6)

# Remove element
unset arr[2]  # Removes element 4

# Remove entire array
unset arr

# Remove suffix from each element
declare -a paths=("/home/user" "/home/admin" "/var/log")
paths=("${paths[@]%/*}")  # Remove after last /
# Result: /home /home /var

# Replace in array
arr=(apple banana cherry)
arr=("${arr[@]/banana/grape}")  # Replace banana with grape
# Result: apple grape cherry

# Convert to string
IFS=","
string="${arr[*]}"  # Join with comma
IFS=$'\n'

# Create array from string
IFS="," read -ra arr <<< "apple,banana,cherry"
# Result: arr=(apple banana cherry)