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
IFSto 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
Joining an Array into a String¶
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¶
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)¶
- Create an array of 5 favorite foods. Print each on its own line using
for. - Using parameter expansion, extract the filename without extension from
"report_2024-01-15.csv". - Split the string
"a:b:c:d"into an array usingIFS=':'andread -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¶
- Write a function that takes a string and returns it in title case (first letter of each word capitalized).
- 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¶
- 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.
- 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.
- 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.