Ditch the awk One-Liners: Parse Config Files with Bash Associative Arrays
Stop spawning awk for every config key. Bash 4's associative arrays parse key=value files in one pass, with lookups that cost nothing — here's the pattern, the portability guard, and the gotchas.
10 Apr 2026, 01:29 UTC

Your deployment script needs three values from a key=value config file: a database host, a port, and an environment name. The classic move is to shell out to awk -F= '$1=="db_host"{print $2}' config.cfg once per key — three subprocesses, three forks, and a sprinkling of quoting hazards. If the file grows to twenty keys, you've got twenty subprocess spawns for what is fundamentally a lookup problem.
Bash 4 (released in 2009) gave us a better tool for this: associative arrays. They're hash maps built into the shell itself, and for simple config parsing they eliminate the external-tool round trip entirely.
The pattern: read once, look up forever
The idea is to slurp the whole file into an associative array in a single pass, then treat lookups as ordinary variable expansions:
#!/usr/bin/env bash
# Requires Bash 4+. Guard first — see below.
declare -A cfg
while IFS='=' read -r key value; do
# Skip blank lines and comments
[[ -z "$key" || "$key" == \#* ]] && continue
cfg["$key"]="$value"
done < config.cfg
echo "Database host: ${cfg[db_host]}"
echo "Port: ${cfg[db_port]:-5432}" # default if unsetRun this as a normal script (bash deploy.sh) with a config.cfg in the working directory. No root needed; the only requirement is read access to the file. A sample config:
# config.cfg
db_host=db.internal.example
db_port=5432
env=stagingAfter the loop, every key is addressable in O(1) time with zero subprocesses. The :-5432 expansion gives you a graceful default when a key is missing — a small touch that replaces a whole if grep -q ... dance.
Why this beats the awk-per-key approach
Three concrete wins:
- Speed. Each
$(awk ...)call forks a subshell and execs awk. Inside a loop over many hosts or files, that overhead is measurable. Associative array lookups are in-process. - Single source of truth. The file is parsed once, consistently. With per-key greps, a malformed line can silently match the wrong key depending on your regex.
- Readability.
${cfg[db_host]}says exactly what it means. A nested awk command in the middle of a script says "the author gave up on clarity."
The portability trade-off you must handle
Associative arrays require Bash 4 or newer. That's fine on virtually every modern Linux distro, but macOS still ships Bash 3.2 as /bin/bash (due to GPLv3 licensing), and old embedded systems are stuck there too. On Bash 3, declare -A fails immediately, which is actually helpful — but only if you fail loudly and early.
Wrap the loader in a function with a version guard:
load_config() {
local file=$1
if (( BASH_VERSINFO[0] < 4 )); then
echo "error: associative arrays need Bash 4+, found $BASH_VERSION" >&2
return 1
fi
declare -gA cfg # -g so it's visible outside the function
local key value
while IFS='=' read -r key value; do
[[ -z "$key" || "$key" == \#* ]] && continue
cfg["$key"]="$value"
done < "$file"
}BASH_VERSINFO is a built-in array where element 0 is the major version — more reliable than parsing the $BASH_VERSION string. Check your environment with echo $BASH_VERSION; anything starting with 4 or 5 works.
Limitations worth knowing before you commit
- Strings only. Every value is a string. If you need nested structure, you're back to JSON plus
jq— and that's fine, because associative arrays were never meant for that. - No hyphens in keys you reference literally.
${cfg[db-host]}is a syntax error; the parser reads it as subtraction. Normalize keys to underscores when loading, or use indirect expansion. - Whitespace is not trimmed.
db_host = db1stores the key asdb_hostwith a trailing space. If your config files come from humans, add a trim step with parameter expansion orextglob. - Duplicate keys silently overwrite. Last one wins, with no warning. For strict configs, check
[[ -v cfg[$key] ]]before assigning and bail out on duplicates.
Verify it actually worked
After loading, dump the map to confirm the parse:
for k in "${!cfg[@]}"; do printf '%s=%s\n' "$k" "${cfg[$k]}"; doneCompare the output against the file. If a key you expect is missing, check for comment lines, whitespace around the =, or CRLF line endings from a Windows-edited file (file config.cfg will tell you; dos2unix fixes it).
The takeaway
If your script targets Bash 4+ — and on modern Linux, it does — stop paying the awk tax for flat key=value configs. One declare -A, one read loop, and every subsequent lookup is free. Add the version guard, trim your whitespace, and you've replaced a fragile pattern with ten lines of self-contained shell. For anything nested or typed, reach for jq instead — the trick is knowing where the line is.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.