Skip to content

String & Array Operations

Bash's string manipulation and arrays are the tools that let you process complex, variable data without reaching for Python or another language.

Learning Objectives

  • Manipulate strings using parameter expansion
  • Work with indexed arrays: create, iterate, slice
  • Work with associative arrays (dictionaries)
  • Use IFS to split and join strings

Advanced String Operations

All Parameter Expansion Patterns in One Place

str="Hello, World!"

echo "${#str}"              # 13           — length
echo "${str:7}"             # World!       — substring from index 7
echo "${str:7:5}"           # World        — substring, length 5
echo "${str,,}"             # hello, world! — lowercase (bash 4+)
echo "${str^^}"             # HELLO, WORLD! — uppercase (bash 4+)
echo "${str/World/Shell}"   # Hello, Shell! — replace first match
echo "${str//l/L}"          # HeLLo, WorLd! — replace all
echo "${str#Hello, }"       # World!        — remove prefix
echo "${str%!}"             # Hello, World  — remove suffix

Splitting Strings with IFS

# Split a comma-separated string into an array
IFS=',' read -ra parts <<< "apple,banana,cherry"
for part in "${parts[@]}"; do
    echo "$part"
done
apple
banana
cherry

Joining an Array into a String

arr=("one" "two" "three")
joined=$(IFS=','; echo "${arr[*]}")
echo "$joined"    # one,two,three

Indexed Arrays

fruits=("apple" "banana" "cherry")    # declare
fruits+=("date")                       # append element
fruits[1]="blueberry"                  # modify element

echo "${fruits[0]}"        # apple        — single element
echo "${fruits[@]}"        # all elements
echo "${#fruits[@]}"       # 4            — length
echo "${!fruits[@]}"       # 0 1 2 3      — indices
echo "${fruits[@]:1:2}"    # blueberry cherry  — slice

Iterating Correctly

for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

Always quote "${arr[@]}"

${arr[@]} without quotes word-splits elements containing spaces into multiple words. "${arr[@]}" preserves each element as a single word, even if it contains spaces.

Array from Command Output

mapfile -t lines < /etc/passwd           # each line → one array element
mapfile -t files < <(find . -name "*.sh")

echo "Total: ${#lines[@]} lines"

Associative Arrays (bash 4+)

Associative arrays use string keys instead of integer indices:

declare -A colors
colors["red"]="#FF0000"
colors["green"]="#00FF00"
colors["blue"]="#0000FF"

echo "${colors["red"]}"         # #FF0000
echo "${!colors[@]}"            # red green blue — keys
echo "${colors[@]}"             # values

for key in "${!colors[@]}"; do
    echo "$key = ${colors[$key]}"
done

Counting Occurrences

declare -A count
while IFS= read -r word; do
    (( count["$word"]++ )) || true
done < <(tr ' ' '\n' < essay.txt)

for word in "${!count[@]}"; do
    echo "${count[$word]} $word"
done | sort -rn | head -10

String Testing

str="hello world"

[[ "$str" == *"world"* ]] && echo "contains 'world'"
[[ "$str" =~ ^[a-z\ ]+$ ]] && echo "only lowercase and spaces"
[[ -z "$str" ]] && echo "empty"
[[ -n "$str" ]] && echo "non-empty"

Common Mistakes

Quoting array expansions

${arr[*]} with single quotes in for treats the whole array as one string. "${arr[@]}" preserves individual elements. Almost always use "${arr[@]}".

declare -A requires bash 4.0+

macOS ships with bash 3.2 (which does not support associative arrays). Always check bash --version and document the minimum version in your script header.


Practice Exercises

Warm-Up (run and observe)

  1. Create an array of 5 favorite foods. Print each on its own line using for.
  2. Using parameter expansion, extract the filename without extension from "report_2024-01-15.csv".
  3. Split the string "a:b:c:d" into an array using IFS=':' and read -ra. Print each element.

Main (write a short script)

Create ~/scripts/word_count.sh that reads a text file and prints the top 10 most frequent words:

#!/usr/bin/env bash
set -euo pipefail

FILE="${1:?Usage: $0 <textfile>}"
declare -A count

while IFS= read -r line; do
    for word in $line; do
        word="${word,,}"          # lowercase
        word="${word//[^a-z]/}"  # strip non-alpha
        [[ -n "$word" ]] || continue
        (( count["$word"]++ )) || true
    done
done < "$FILE"

for word in "${!count[@]}"; do
    echo "${count[$word]} $word"
done | sort -rn | head -10

Stretch

  1. Write a function that takes a string and returns it in title case (first letter of each word capitalized).
  2. How do you check if a value exists in an array? (Hint: there is no built-in operator; write a loop or use a pattern.)

Interview Questions

  1. What is the difference between ${arr[@]} and ${arr[*]}?
Show answer

When unquoted, both expand to all elements joined by spaces. When double-quoted: "${arr[@]}" expands to separate quoted words (one per element, preserving spaces within elements). "${arr[*]}" expands to a single string with elements joined by the first character of IFS. Use "${arr[@]}" when iterating or passing to other commands.

  1. How do you create an associative array in bash?
Show answer

You must declare it explicitly: declare -A mymap. Then assign: mymap["key"]="value". Without declare -A, bash treats it as a regular indexed array and string keys are treated as 0. Associative arrays require bash 4.0 or later.

  1. How do you iterate over associative array keys and values?
Show answer

for key in "${!mymap[@]}"; do echo "$key = ${mymap[$key]}"; done. ${!mymap[@]} gives all keys. ${mymap[@]} gives all values (without their keys). ${#mymap[@]} gives the count of entries.


day02-part1-error-handling | day03-part1-file-operations