配置管理:配置文件解析与环境变量 | Configuration Management: Config Files and Environment Variables
章节概述
本章全面讲解 Shell 脚本中的配置管理策略,包括 KEY=VALUE 配置文件解析、INI/TOML 格式解析、环境变量优先级机制、.env 文件使用、配置文件模板系统,以及与 C 语言配置文件读取的对比分析。
核心理念:配置是代码与环境之间的契约。良好的配置管理让你的脚本在开发、测试、生产环境中无缝切换,零修改部署。
第1节:KEY=VALUE 配置文件解析
标准配置文件格式
# config/app.conf
APP_NAME="MyApp"
APP_VERSION="1.0.0"
APP_PORT=8080
APP_HOST="0.0.0.0"
APP_DEBUG=true
APP_LOG_LEVEL="info"
DATABASE_URL="mysql://localhost:3306/mydb"解析函数
#!/usr/bin/env bash
# 读取单个配置值
read_config() {
local file="${1}"
local key="${2}"
local default="${3:-}"
if [ ! -f "${file}" ]; then
echo "${default}"
return 1
fi
local value
value=$(grep -E "^${key}=" "${file}" | head -1 | cut -d'=' -f2-)
# 去除引号
value="${value%"}"
value="${value#"}"
value="${value%'}"
value="${value#'}"
if [ -z "${value}" ]; then
echo "${default}"
else
echo "${value}"
fi
}
# 加载所有配置到环境变量
load_config() {
local file="${1}"
local prefix="${2:-}"
if [ ! -f "${file}" ]; then
echo "Config file not found: ${file}" >&2
return 1
fi
while IFS='=' read -r key value; do
[[ "${key}" =~ ^[[:space:]]*# ]] && continue
[[ -z "${key}" ]] && continue
value="${value%"}"
value="${value#"}"
value="${value%'}"
value="${value#'}"
if [ -n "${prefix}" ]; then
key="${prefix}_${key}"
fi
export "${key}=${value}"
done < "${file}"
}使用示例
# 读取单个值
APP_PORT=$(read_config "config/app.conf" "APP_PORT" "8080")
echo "Server running on port: ${APP_PORT}"
# 加载所有配置
load_config "config/app.conf"
echo "App: ${APP_NAME}"
# 带前缀加载(避免命名冲突)
load_config "config/database.conf" "DB"
echo "Database: ${DB_DATABASE_URL}"第2节:INI/TOML 格式解析
INI 文件解析
# config/settings.ini
[database]
host=localhost
port=3306
name=mydb
user=admin
[server]
host=0.0.0.0
port=8080
workers=4
[logging]
level=info
file=/var/log/app.logparse_ini() {
local file="${1}"
local section=""
while IFS='=' read -r key value || [ -n "${key}" ]; do
key=$(echo "${key}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
value=$(echo "${value}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ "${key}" =~ ^[[:space:]]*\; ]] && continue
[[ "${key}" =~ ^[[:space:]]*\# ]] && continue
[[ -z "${key}" ]] && continue
if [[ "${key}" =~ ^\[(.+)\]$ ]]; then
section="${BASH_REMATCH[1]}"
continue
fi
value="${value%"}"
value="${value#"}"
value="${value%'}"
value="${value#'}"
export "${section:+${section}_}${key}=${value}"
done < "${file}"
}
# 使用
parse_ini "config/settings.ini"
echo "${database_host}" # localhost
echo "${server_port}" # 8080TOML 文件解析(简单格式)
# config/config.toml
[app]
name = "MyApp"
version = "1.0.0"
debug = true
[database]
host = "localhost"
port = 3306parse_toml() {
local file="${1}"
local section=""
while IFS='=' read -r key value || [ -n "${key}" ]; do
key=$(echo "${key}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
value=$(echo "${value}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ "${key}" =~ ^\# ]] && continue
[[ -z "${key}" ]] && continue
if [[ "${key}" =~ ^\[(.+)\]$ ]]; then
section="${BASH_REMATCH[1]}"
continue
fi
value="${value%"}"
value="${value#"}"
value="${value%'}"
value="${value#'}"
case "${value}" in
true) value=1 ;;
false) value=0 ;;
esac
export "${section:+${section}_}${key}=${value}"
done < "${file}"
}第3节:环境变量优先级机制
优先级顺序(从低到高)
# 优先级:默认值 < 配置文件 < 环境变量 < 命令行参数
# 1. 默认值
APP_PORT="${APP_PORT:-8080}"
# 2. 配置文件
if [ -f "config/app.conf" ]; then
file_port=$(read_config "config/app.conf" "APP_PORT")
[ -n "${file_port}" ] && APP_PORT="${file_port}"
fi
# 3. 环境变量
# APP_PORT 已经在环境中,直接使用
# 4. 命令行参数
while [[ $# -gt 0 ]]; do
case "${1}" in
--port)
APP_PORT="${2}"
shift 2
;;
*)
shift
;;
esac
done优先级实现函数
get_config() {
local key="${1}"
local default="${2:-}"
local config_file="${3:-config/app.conf}"
local value="${default}"
# 优先级1:配置文件
if [ -f "${config_file}" ]; then
local file_value
file_value=$(read_config "${config_file}" "${key}")
[ -n "${file_value}" ] && value="${file_value}"
fi
# 优先级2:环境变量
local env_value="${!key:-}"
[ -n "${env_value}" ] && value="${env_value}"
echo "${value}"
}
# 使用
PORT=$(get_config "APP_PORT" "8080" "config/app.conf")优先级对照表
| 优先级 | 来源 | 覆盖方式 | 示例 |
|---|---|---|---|
| 1 (最低) | 默认值 | 硬编码在脚本中 | PORT=8080 |
| 2 | 配置文件 | 编辑文件 | APP_PORT=9090 |
| 3 | 环境变量 | export 设置 | export APP_PORT=7070 |
| 4 (最高) | 命令行参数 | 传参 | ./script --port 6060 |
第4节:.env 文件使用
.env 文件格式
# .env - 环境变量配置文件
DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp
DB_USER=admin
DB_PASSWORD=secret123
APP_ENV=development
APP_DEBUG=true
APP_SECRET_KEY=abc123def456
API_BASE_URL=https://api.example.com
API_TIMEOUT=30.env 加载函数
load_dotenv() {
local env_file="${1:-.env}"
if [ ! -f "${env_file}" ]; then
echo "Warning: ${env_file} not found" >&2
return 1
fi
while IFS='=' read -r key value; do
[[ "${key}" =~ ^[[:space:]]*# ]] && continue
[[ -z "${key}" ]] && continue
key=$(echo "${key}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
value="${value%"}"
value="${value#"}"
value="${value%'}"
value="${value#'}"
# 仅在环境变量未设置时加载
if [ -z "${!key:-}" ]; then
export "${key}=${value}"
fi
done < "${env_file}"
}
# 强制覆盖模式
load_dotenv_force() {
local env_file="${1:-.env}"
while IFS='=' read -r key value; do
[[ "${key}" =~ ^[[:space:]]*# ]] && continue
[[ -z "${key}" ]] && continue
key=$(echo "${key}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
value="${value%"}"
value="${value#"}"
value="${value%'}"
value="${value#'}"
export "${key}=${value}"
done < "${env_file}"
}.env 安全实践
# .env.example(提交到版本控制)
DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp
DB_USER=admin
DB_PASSWORD=changeme
# .gitignore 中添加
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.production" >> .gitignore
# 加载 .env(不覆盖现有环境变量)
load_dotenv ".env"
# 然后加载环境特定配置
load_dotenv ".env.${APP_ENV:-development}"第5节:配置文件模板与多环境
模板系统
render_template() {
local template="${1}"
local output="${2}"
if [ ! -f "${template}" ]; then
echo "Template not found: ${template}" >&2
return 1
fi
local content
content=$(cat "${template}")
local vars
vars=$(grep -oE '\$\{[A-Z_]+\}' "${template}" | sort -u | tr -d '${}}')
for var in ${vars}; do
local value="${!var:-}"
content="${content//\${${var}}/${value}}"
done
echo "${content}" > "${output}"
}多环境配置
#!/usr/bin/env bash
# 环境检测
detect_environment() {
local env="${APP_ENV:-development}"
case "${HOSTNAME}" in
*-prod*) env="production" ;;
*-stg*) env="staging" ;;
*-dev*) env="development" ;;
esac
echo "${env}"
}
# 加载环境配置
load_environment() {
local env
env=$(detect_environment)
local base_config="config/app.conf"
local env_config="config/environments/${env}.conf"
# 先加载基础配置
if [ -f "${base_config}" ]; then
load_config "${base_config}"
fi
# 再加载环境配置(覆盖基础配置)
if [ -f "${env_config}" ]; then
load_config "${env_config}"
fi
export APP_ENV="${env}"
log_info "Loaded config for environment: ${env}"
}第6节:C 配置文件读取对比
Bash vs C 配置解析对比
| 特性 | Bash | C |
|---|---|---|
| 速度 | 较慢 | 快 |
| 依赖 | 无 | 需要库 |
| 复杂度 | 简单 | 较高 |
| 类型支持 | 仅字符串 | 多种类型 |
| 错误处理 | 简单 | 完善 |
| 适用场景 | 脚本部署 | 应用程序 |
C 语言配置解析示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char key[256];
char value[1024];
} ConfigEntry;
typedef struct {
ConfigEntry entries[100];
int count;
} Config;
void parse_config(Config *config, const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) return;
char line[2048];
config->count = 0;
while (fgets(line, sizeof(line), file)) {
if (line[0] == '#' || line[0] == '\n') continue;
char *eq = strchr(line, '=');
if (!eq) continue;
*eq = '\0';
char *key = line;
char *value = eq + 1;
value[strcspn(value, "\n")] = '\0';
strncpy(config->entries[config->count].key, key, 255);
strncpy(config->entries[config->count].value, value, 1023);
config->count++;
}
fclose(file);
}
const char *get_config(Config *config, const char *key) {
for (int i = 0; i < config->count; i++) {
if (strcmp(config->entries[i].key, key) == 0) {
return config->entries[i].value;
}
}
return "";
}Bash 调用 C 配置程序
# 编译 C 配置解析器
gcc -o config_parser config_parser.c
# Bash 调用
PORT=$(./config_parser config.conf "APP_PORT")
HOST=$(./config_parser config.conf "APP_HOST")
echo "Server: ${HOST}:${PORT}"本节帮助你构建灵活的配置管理系统,实现一处配置、多处运行的目标。