Linux Basic — Working with Strings Practice Exercises
Working with Strings — Practice Exercises
Five exercises that cover every operator in the chapter. Try each yourself before peeking.
Exercise 1 — Required Environment Variables
Goal: At the top of a script, enforce that API_TOKEN, REGION, and ENV are all set and non-empty. Print one error per missing var with a clear name, then exit. If all are present, print a confirmation.
💡 Show Answer
#!/usr/bin/env bash
set -u
missing=()
for v in API_TOKEN REGION ENV; do
[[ -n "${!v:-}" ]] || missing+=("$v")
done
if ((${#missing[@]})); then
printf 'Missing required env var: %s\n' "${missing[@]}" >&2
exit 1
fi
echo "All required env vars are set."
${!v} is indirect expansion: it expands to the value of the variable whose name is $v. The :- keeps set -u happy.
A more compact one-liner per var:
: "${API_TOKEN:?API_TOKEN required}"
: "${REGION:?REGION required}"
: "${ENV:?ENV required}"
…but it exits on the first missing var rather than reporting all of them.
Exercise 2 — Native basename and dirname
Goal: Write two functions, bname and dname, that mimic basename and dirname using only parameter expansion — no forks. They must handle:
- A normal path:
/var/log/nginx/access.log - A trailing slash:
/var/log/nginx/ - A bare filename:
hello.txt - An empty input
💡 Show Answer
bname() {
local p=${1%/} # strip any trailing slash
[[ -z "$p" ]] && { echo /; return; } # input was just "/"
echo "${p##*/}"
}
dname() {
local p=${1%/}
[[ -z "$p" ]] && { echo /; return; }
local d=${p%/*}
[[ "$d" == "$p" ]] && d=. # no slash in input
echo "$d"
}
for f in "/var/log/nginx/access.log" "/var/log/nginx/" "hello.txt" "/" ""; do
printf '%-30s -> bname=%-15s dname=%s\n' "[$f]" "$(bname "$f")" "$(dname "$f")"
done
# [/var/log/nginx/access.log] -> bname=access.log dname=/var/log/nginx
# [/var/log/nginx/] -> bname=nginx dname=/var/log
# [hello.txt] -> bname=hello.txt dname=.
# [/] -> bname=/ dname=/
# [] -> bname=/ dname=/
Edge cases handled: trailing slash via ${1%/}, empty / bare slash, dirname of a single component returns ..
Exercise 3 — Slugify a Title
Goal: Convert any blog-post title into a URL-safe slug:
- lowercase
- spaces →
- - strip all characters except
a-z,0-9,- - collapse multiple consecutive
-into one - trim leading/trailing
-
Input: " Hello, World!! -- 2026 Update "
Expected: hello-world-2026-update
💡 Show Answer
slugify() {
local s=${1,,} # lowercase
s=${s// /-} # spaces to hyphens
# Strip everything not a-z, 0-9, or -
local out=""
local i ch
for ((i=0; i<${#s}; i++)); do
ch=${s:i:1}
[[ "$ch" =~ [a-z0-9-] ]] && out+="$ch"
done
# Collapse multiple hyphens
while [[ "$out" == *--* ]]; do
out=${out//--/-}
done
# Trim leading / trailing hyphens
out=${out#-}
out=${out%-}
echo "$out"
}
slugify " Hello, World!! -- 2026 Update "
# hello-world-2026-update
slugify "Café & Cookies"
# caf-cookies (multi-byte é dropped — for codepoint support you'd need a different tool)
Or the same with tr + sed (if you’re allowed):
slugify_fast() {
echo "${1,,}" \
| tr -c 'a-z0-9' '-' \
| tr -s '-' \
| sed 's/^-//;s/-$//'
}
Exercise 4 — Parse a key=value Config File
Goal: Read a config file with key=value lines (and # comments) into an associative array, then print every key/value pair. Handle:
- Blank lines
- Lines starting with
#(comment) - Whitespace around
= - Quoted values:
name="Alice Smith"(strip the quotes)
Setup:
cat > /tmp/app.conf <<'EOF'
# Sample config
host = example.com
port=8080
name = "Alice Smith"
debug=true
EOF
💡 Show Answer
declare -A cfg
while IFS= read -r line; do
# Skip blanks and comments
[[ -z "${line// }" ]] && continue
[[ "$line" =~ ^[[:space:]]*# ]] && continue
# Split on first =
key=${line%%=*}
val=${line#*=}
# Trim whitespace
key=${key#"${key%%[![:space:]]*}"}; key=${key%"${key##*[![:space:]]}"}
val=${val#"${val%%[![:space:]]*}"}; val=${val%"${val##*[![:space:]]}"}
# Strip surrounding quotes
[[ "$val" == \"*\" ]] && val=${val:1:-1}
cfg["$key"]="$val"
done < /tmp/app.conf
for k in "${!cfg[@]}"; do
printf '%-8s = [%s]\n' "$k" "${cfg[$k]}"
done
# host = [example.com]
# port = [8080]
# name = [Alice Smith]
# debug = [true]
The two key=${key#"${key%%[![:space:]]*}"} lines are the cheap trim idiom — strip leading and trailing whitespace using pattern removal.
Exercise 5 — CSV Line → Pretty Table Row
Goal: Given a comma-separated string of fields, print them as a fixed-width table row. Trim whitespace around each field, title-case the first field, and pad all fields to 12 chars.
Input: " alice , developer , eu-west-1"
Expected:
| Alice | developer | eu-west-1 |
💡 Show Answer
pretty_row() {
local IFS=, fields
read -ra fields <<< "$1"
local out="|"
for i in "${!fields[@]}"; do
local f=${fields[i]}
# Trim
f=${f#"${f%%[![:space:]]*}"}
f=${f%"${f##*[![:space:]]}"}
# Title-case first field
((i == 0)) && f=${f^}
printf -v cell ' %-12s |' "$f"
out+=$cell
done
echo "$out"
}
pretty_row " alice , developer , eu-west-1"
# | Alice | developer | eu-west-1 |
printf -v cell stores the formatted output into a variable instead of printing — much faster than cell=$(printf …) in a loop.
What’s Next?
You can manipulate any string natively. The next chapter graduates to the heavy lifters of UNIX text processing — sed and awk in scripts: when to reach for them, the patterns that come up in 90% of scripts, and Bash’s own regex =~ for the cases where you can keep it native.
Continue → sed & awk in Scripts