Shell Scripting Bash Configuration Intermediate May 2026

Shell Scripting Configuration File Management

Read and write INI files, key-value configs, and .env files from bash scripts. Validate config values, manage environment-specific settings, and safely handle secrets without hardcoding them.

Production scripts never hardcode hostnames, passwords, or environment-specific values. Instead, they read from config files that are separate from the code, can be different per environment, and are managed independently. This page covers every config pattern you'll encounter in real bash projects.

BASH
# .env file format — simple KEY=VALUE pairs
# /etc/myapp/production.env
DB_HOST=prod-db-01.internal
DB_PORT=3306
DB_NAME=myapp
DB_USER=app_user
DB_PASS=s3cur3p@ssw0rd
LOG_LEVEL=WARN
MAX_WORKERS=16

# ── Source .env file ──────────────────────────────────────
# Method 1: simple source (variables are exported)
set -a                          # auto-export all vars
source /etc/myapp/production.env
set +a                          # stop auto-export

# Method 2: safe source with validation
load_env() {
  local env_file="${1}"
  [[ -f "${env_file}" ]] || { echo "ERROR: ${env_file} not found" >&2; return 1; }
  [[ -r "${env_file}" ]] || { echo "ERROR: ${env_file} not readable" >&2; return 1; }

  # Only export valid KEY=VALUE lines — ignore comments, blank lines
  while IFS= read -r line; do
    [[ "${line}" =~ ^[[:space:]]*# ]] && continue  # skip comments
    [[ "${line}" =~ ^[[:space:]]*$ ]] && continue  # skip blank lines
    [[ "${line}" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]] || continue  # skip invalid
    export "${line?}"
  done < "${env_file}"
}

load_env /etc/myapp/production.env
BASH
# Config file: /etc/myapp/config.ini
# [database]
# host = prod-db-01
# port = 3306
# [server]
# workers = 8

# ── Read a single value by section and key ────────────────
get_ini_value() {
  local file="${1}"
  local section="${2}"
  local key="${3}"

  awk -F ' *= *' \
    -v section="${section}" \
    -v key="${key}" '
    /^\[/ { in_section = ($0 == "[" section "]") }
    in_section && $1 == key { print $2; exit }
  ' "${file}"
}

DB_HOST=$(get_ini_value /etc/myapp/config.ini database host)
DB_PORT=$(get_ini_value /etc/myapp/config.ini database port)
WORKERS=$(get_ini_value /etc/myapp/config.ini server workers)

echo "DB: ${DB_HOST}:${DB_PORT}, Workers: ${WORKERS}"

# ── Write / update a value in config ─────────────────────
set_config_value() {
  local file="${1}"
  local key="${2}"
  local value="${3}"

  if grep -q "^${key}=" "${file}"; then
    sed -i "s|^${key}=.*|${key}=${value}|" "${file}"
  else
    echo "${key}=${value}" >> "${file}"
  fi
}

set_config_value /etc/myapp/config.env LOG_LEVEL DEBUG
BASH
#!/usr/bin/env bash
# validate_config.sh — Validate all required config values

validate_config() {
  local -i errors=0

  # Required: must be set and non-empty
  for var in DB_HOST DB_USER DB_PASS APP_ENV; do
    [[ -n "${!var:-}" ]] || {
      echo "ERROR: ${var} is required" >&2
      (( errors++ ))
    }
  done

  # Numeric validation
  [[ "${DB_PORT:-3306}" =~ ^[0-9]+$ ]] || {
    echo "ERROR: DB_PORT must be numeric" >&2; (( errors++ ))
  }
  (( DB_PORT >= 1 && DB_PORT <= 65535 )) 2>/dev/null || {
    echo "ERROR: DB_PORT must be 1-65535" >&2; (( errors++ ))
  }

  # Enum validation
  case "${APP_ENV:-}" in
    development|staging|production) ;;
    *) echo "ERROR: APP_ENV must be development|staging|production" >&2
       (( errors++ )) ;;
  esac

  # File path validation
  [[ -d "${LOG_DIR:-/var/log}" ]] || {
    echo "ERROR: LOG_DIR '${LOG_DIR}' does not exist" >&2; (( errors++ ))
  }

  if (( errors > 0 )); then
    echo "Config validation failed with ${errors} error(s)" >&2
    return 1
  fi
  echo "Config validation passed"
}

load_env "/etc/myapp/${APP_ENV:-development}.env"
validate_config || exit 1
bash — config validation
vriddh@prod-01:~/scripts$APP_ENV=staging ./validate_config.sh
ERROR: DB_PASS is required
Config validation failed with 1 error(s)
vriddh@prod-01:~/scripts$DB_PASS=secret APP_ENV=staging ./validate_config.sh
Config validation passed
⚠ Never commit secrets — Config files with passwords must never be committed to git. Use .gitignore to exclude *.env and config/*.env. Use a secrets manager (HashiCorp Vault, AWS Secrets Manager) or environment variables injected by your CI/CD system for production credentials.
✔ Config management rules — Always separate config from code. Always validate required values at startup — fail fast with clear messages. Use environment-specific files (production.env, staging.env). Never store production credentials in scripts or git. Always provide sensible defaults for optional values using ${VAR:-default}.