What you will learn
Load a simple INI-style config file into a Bash associative array (declare -A) safely — without source-ing arbitrary code — so a script can look up settings by key at runtime.
Before you begin
Requires Bash 4+. This approach is for flat key=value files with optional [section] headers; for deeply nested config, use jq/yq against JSON or YAML instead.
1. Parse without executing the file as shell code
#!/usr/bin/env bash
set -Eeuo pipefail
declare -A CONFIG=()
load_config() {
local file="$1" section="" line key value
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%%#*}" # strip trailing comments
line="${line#"${line%%[![:space:]]*}"}" # trim leading whitespace
line="${line%"${line##*[![:space:]]}"}" # trim trailing whitespace
[[ -z "$line" ]] && continue
if [[ "$line" =~ ^\[(.+)\]$ ]]; then
section="${BASH_REMATCH[1]}"
continue
fi
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
value="${value%\"}"; value="${value#\"}" # strip surrounding quotes if present
CONFIG["${section:+${section}.}${key}"]="$value"
fi
done < "$file"
}
Reading line-by-line with a regex match (BASH_REMATCH) means a malicious or malformed line is simply skipped or misparsed, never executed — unlike source, which runs the file as shell code.
2. Sample config and lookup
# app.conf
[database]
host=db01.internal
port=5432
[logging]
level="debug"
load_config "app.conf"
echo "db host: ${CONFIG[database.host]}"
echo "db port: ${CONFIG[database.port]}"
echo "log level: ${CONFIG[logging.level]}"
# Safe lookup with a default for an optional key.
timeout="${CONFIG[database.timeout]:-30}"
echo "timeout (default applied): ${timeout}"
3. Validate required keys exist before using them
require_keys() {
local missing=0
for k in "$@"; do
if [[ -z "${CONFIG[$k]+set}" ]]; then
echo "missing required config key: ${k}" >&2
missing=1
fi
done
(( missing == 0 ))
}
require_keys "database.host" "database.port" || exit 1
${CONFIG[$k]+set} tests whether the key exists at all (even with an empty value), which is different from -z "${CONFIG[$k]}" — the latter would also fire on a key that's present but deliberately blank.
Example output
db host: db01.internal
db port: 5432
log level: debug
timeout (default applied): 30
Verify the result
Add a line with no = and a line that's just a comment to the config file and confirm load_config skips both without error, leaving CONFIG populated only with valid entries.
Troubleshooting
If section-prefixed keys aren't found, double-check you're indexing with the literal dot, e.g. CONFIG[database.host] — Bash associative array keys are plain strings, there's no nested structure.
Next steps
Use this to back multi-env-deploy-orchestrator's environment files with sections, or any script that currently hardcodes settings inline.