1. 当脚本变成"复制粘贴"地狱时
运维工程师小王最近遇到件烦心事:每天要维护二十多个相似的备份脚本。每次修改参数就像玩"大家来找茬",稍不留神就会漏改某个文件。上周因为某个脚本里的路径没更新,导致整个数据库备份失败。这不正是典型的代码复用性差导致的运维事故吗?
2. 函数:给代码碎片穿针引线
(技术栈:Bash Shell 5.0+)
让我们从最简单的日志备份需求开始。原始脚本可能是这样的:
#!/bin/bash
# 备份应用日志
LOG_DIR="/var/log/myapp"
BACKUP_DIR="/backup/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
tar -czf $BACKUP_DIR/app_logs.tar.gz $LOG_DIR/*.log
# 备份系统日志
SYSLOG_DIR="/var/log/system"
mkdir -p $BACKUP_DIR
tar -czf $BACKUP_DIR/system_logs.tar.gz $SYSLOG_DIR/*.log
当我们需要复用打包逻辑时,可以用函数改造:
#!/bin/bash
# 定义打包函数
package_logs() {
local src_dir=$1 # 源目录(第一个参数)
local prefix=$2 # 备份文件前缀(第二个参数)
local backup_root="/backup/$(date +%Y%m%d)"
mkdir -p "$backup_root"
tar -czf "${backup_root}/${prefix}_logs.tar.gz" "${src_dir}"/*.log
}
# 调用函数执行实际任务
package_logs "/var/log/myapp" "application"
package_logs "/var/log/system" "system"
应用场景:适合处理重复操作但参数不同的任务,比如批量文件处理、多服务启停等
技术优势:
- 消除重复代码块
- 参数化配置提高灵活性
- 修改只需调整函数定义
注意事项:
- 使用local定义局部变量避免污染全局空间
- 函数应保持单一职责原则
- 通过return code传递执行状态
3. 模块化:把脚本变成乐高积木
(技术栈:Bash Shell 4.0+)
当项目规模扩大时,可以把通用功能拆分到独立文件。假设我们创建logging_utils.sh
:
#!/bin/bash
# 日志工具模块
init_backup_dir() {
export BACKUP_ROOT="/backup/$(date +%Y%m%d)"
mkdir -p "$BACKUP_ROOT"
}
compress_files() {
local target_dir=$1
local output_name=$2
tar -czf "${BACKUP_ROOT}/${output_name}.tar.gz" -C "$target_dir" .
}
clean_old_backups() {
local retention_days=$1
find "/backup" -type d -mtime +$retention_days -exec rm -rf {} \;
}
主脚本通过source引入:
#!/bin/bash
source "$(dirname "$0")/logging_utils.sh"
init_backup_dir
compress_files "/var/log/myapp" "app_logs"
compress_files "/var/log/system" "sys_logs"
clean_old_backups 7
应用场景:适合跨项目共享代码、构建工具链、封装复杂操作
技术优势:
- 实现真正的代码复用
- 各模块独立维护
- 通过命名空间管理功能
注意事项:
- 注意文件路径引用问题
- 避免循环引用
- 使用unset及时释放资源
4. 参数魔术:让脚本学会七十二变
(技术栈:Bash Shell 4.0+)
通过参数化改造提升灵活性:
#!/bin/bash
# 通用备份脚本
# 解析参数
while getopts "s:p:r:" opt; do
case $opt in
s) source_dir="$OPTARG" ;;
p) prefix_name="$OPTARG" ;;
r) retention_days="$OPTARG" ;;
*) echo "Invalid option"; exit 1 ;;
esac
done
# 参数校验
[[ -z "$source_dir" ]] && { echo "必须指定源目录"; exit 1; }
# 使用带默认值的参数
: ${prefix_name:=default_backup}
: ${retention_days:=7}
# 主逻辑
backup_dir="/backup/$(date +%Y%m%d)"
mkdir -p "$backup_dir"
tar -czf "${backup_dir}/${prefix_name}.tar.gz" -C "$source_dir" .
find "/backup" -type d -mtime +$retention_days -exec rm -rf {} \;
应用场景:需要频繁调整配置参数的场景,如定时任务、CI/CD流水线
技术优势:
- 提升脚本通用性
- 降低使用门槛
- 支持配置默认值
注意事项:
- 必须包含参数校验逻辑
- 重要参数建议设置默认值
- 使用getopts规范参数解析
5. 组合拳:构建你的脚本兵器库
将上述方法结合使用:
#!/bin/bash
# 主程序:backup_manager.sh
# 加载工具库
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib"
source "${LIB_DIR}/logging_utils.sh"
source "${LIB_DIR}/notify_utils.sh"
# 主函数
main() {
local config_file=$1
source "$config_file" || exit 1
init_backup_system
perform_backups
send_notification "Backup completed"
}
# 启动程序
main "$@"
技术优势:
- 分层架构清晰
- 功能模块可插拔
- 配置与逻辑分离
6. 技术方案的AB面
优点对比:
- 函数封装:开发成本低,适合小型项目
- 模块化:维护性好,适合团队协作
- 参数化:灵活性高,适合通用工具
潜在缺陷:
- 函数间存在作用域污染风险
- 模块加载增加启动耗时
- 过度抽象可能降低可读性
避坑指南:
- 使用shellcheck进行静态检查
- 重要操作添加dry-run模式
- 保持函数不超过50行
- 为共享函数添加版本控制
7. 总结:让脚本拥有超能力
通过函数封装、模块拆分、参数优化三板斧,我们可以让Bash脚本焕发新生。就像给传统武术加上现代格斗技巧,既保留Shell脚本的轻量优势,又获得现代编程的工程化特性。记住:当脚本超过300行时,就该考虑用Python重写了——但在此之前,这些优化技巧足以帮你打造出高效的Shell武器库。
下次当你的手指准备按下Ctrl+C/Ctrl+V时,不妨停下来想想:这个代码片段值得被封装吗?毕竟,优秀的工程师都在用脑子写代码,而不是用手。