You’re looping over command output and the loop variable sometimes has spaces in it. You’re declaring a list of commands but it’s a mess of escaped quotes. You’re trying to parse structured data in bash and you’re using awk.
Arrays are the answer. They make bash scripts actually readable. Most people don’t use them. I’ve seen scripts where people fake arrays with grep and cut when they could just use bash’s built-in array syntax.
Here’s the thing: once you start using arrays, you can’t write bash scripts without them.
Declaring Arrays
Indexed array (default):
#!/bin/bash
# Method 1: explicit declarationdeclare -a fruits=("apple" "banana" "cherry")
# Method 2: implicitfruits=("apple" "banana" "cherry")
# Method 3: add elements individuallyservers[0]="web1.example.com"servers[1]="web2.example.com"servers[2]="db1.example.com"Associative array (like a dict):
#!/bin/bash
declare -A configconfig[database]="postgres"config[port]="5432"config[username]="admin"
echo "${config[database]}" # postgresecho "${config[port]}" # 5432Accessing Array Elements
#!/bin/bash
arr=("first" "second" "third")
# Single elementecho "${arr[0]}" # firstecho "${arr[2]}" # thirdecho "${arr[-1]}" # third (last element, bash 4.3+)
# All elementsecho "${arr[@]}" # first second thirdecho "${arr[*]}" # first second third (usually same)
# Lengthecho "${#arr[@]}" # 3The @ vs * difference matters when you quote:
#!/bin/bash
arr=("file1.txt" "file2.txt" "file with spaces.txt")
# WRONG: ${arr[*]} treats array as single wordfor file in ${arr[*]}; do echo "$file"done# Output:# file1.txt# file2.txt# file# with# spaces.txt
# RIGHT: "${arr[@]}" treats each element as separate wordfor file in "${arr[@]}"; do echo "$file"done# Output:# file1.txt# file2.txt# file with spaces.txtAlways use "${arr[@]}" when iterating.
Iterating Over Arrays
#!/bin/bash
commands=("ls -la" "echo hello" "pwd")
for cmd in "${commands[@]}"; do echo "Running: $cmd" eval "$cmd"doneWith index:
#!/bin/bash
servers=("web1" "web2" "db1")
for i in "${!servers[@]}"; do echo "Server $i: ${servers[$i]}"done$ bash loop_with_index.shServer 0: web1Server 1: web2Server 2: db1The "${!servers[@]}" gives you the array indices.
Appending and Deleting
Append:
#!/bin/bash
arr=("one" "two")
arr+=("three") # Append single elementarr+=("four" "five") # Append multiple
echo "${arr[@]}" # one two three four fiveDelete element:
#!/bin/bash
arr=("one" "two" "three" "four")
unset arr[1] # Delete index 1
echo "${arr[@]}" # one three fourecho "${#arr[@]}" # 3 (deleted element doesn't count)Be careful: indices don’t renumber after deletion.
Clear entire array:
#!/bin/bash
arr=("one" "two" "three")unset arr
echo "${arr[@]}" # EmptySlicing Arrays
Extract a portion of an array:
#!/bin/bash
arr=("a" "b" "c" "d" "e")
# Get 3 elements starting at index 1slice=("${arr[@]:1:3}")echo "${slice[@]}" # b c dReal-World Example 1: Safe Command Execution
#!/bin/bash
# Array of commands to run, in ordersteps=( "echo 'Building project...'" "npm run build" "npm run test" "npm run lint")
for i in "${!steps[@]}"; do echo "Step $((i + 1))/${#steps[@]}: ${steps[$i]}" eval "${steps[$i]}" || { echo "Step $((i + 1)) failed" exit 1 }done
echo "All steps completed"Much cleaner than a big if-then ladder.
Real-World Example 2: Associative Array for Config
#!/bin/bash
declare -A nginx_confignginx_config[server_name]="example.com"nginx_config[listen]="80"nginx_config[upstream]="localhost:3000"nginx_config[client_max_body_size]="10m"
cat <<-EOFserver { server_name ${nginx_config[server_name]}; listen ${nginx_config[listen]}; client_max_body_size ${nginx_config[client_max_body_size]};
location / { proxy_pass http://${nginx_config[upstream]}; }}EOFCleaner than a bunch of variables.
Real-World Example 3: Reading Command Output Safely
Avoid word-splitting disasters:
#!/bin/bash
# WRONG: word splitting breaks filenamesfor file in $(find . -name "*.txt"); do echo "File: $file"done
# RIGHT: read into arraywhile IFS= read -r -d '' file; do arr+=("$file")done < <(find . -name "*.txt" -print0)
for file in "${arr[@]}"; do echo "File: $file"doneThe while read loop populates an array safely, even with spaces and special characters.
Real-World Example 4: Parallel Execution
#!/bin/bash
servers=("web1.example.com" "web2.example.com" "db1.example.com")pids=()
# Start tasks in parallelfor server in "${servers[@]}"; do ssh "$server" "long-running-task" & pids+=($!) # Save the PIDdone
# Wait for all to completefor pid in "${pids[@]}"; do wait "$pid" if [ $? -ne 0 ]; then echo "Task failed: $pid" fidone
echo "All tasks done"Arrays of PIDs make parallel execution manageable.
Arrays vs String Splitting
BAD (fragile string splitting):
#!/bin/bash
servers="web1,web2,web3"IFS=',' read -ra arr <<< "$servers"
for server in "${arr[@]}"; do echo "Server: $server"doneThis works for simple cases but breaks with edge cases (spaces, special chars).
GOOD (explicit array):
#!/bin/bash
declare -a servers=("web1" "web2" "web3")
for server in "${servers[@]}"; do echo "Server: $server"doneClear intent, no hidden splitting rules.
Debugging Arrays
Print array contents nicely:
#!/bin/bash
arr=("one" "two" "three")
# Method 1: simpledeclare -p arr
# Method 2: custom formatfor i in "${!arr[@]}"; do echo " [$i] = ${arr[$i]}"done$ bash debug_array.shdeclare -a arr=([0]="one" [1]="two" [2]="three") [0] = one [1] = two [2] = threeThe Pattern: Arrays Make Scripts Readable
#!/bin/bashset -euo pipefail
# Indexed arraydeclare -a files=()while IFS= read -r -d '' file; do files+=("$file")done < <(find . -name "*.txt" -print0)
# Processfor file in "${files[@]}"; do echo "Processing: $file"done
# Associative array for configdeclare -A configconfig[input_dir]="./input"config[output_dir]="./output"config[format]="json"
# Use configmkdir -p "${config[output_dir]}"
echo "Config:"for key in "${!config[@]}"; do echo " $key = ${config[$key]}"doneThat’s readable. That’s maintainable. That’s bash done right.
The One Thing
Your next bash script shouldn’t have this:
# NOitems="one,two,three"# ... hand-parsing with grep/cutIt should have this:
# YESdeclare -a items=("one" "two" "three")for item in "${items[@]}"; do # ...doneArrays aren’t fancy. They’re basic. Use them.