当前位置:首页>Linux>Linux运维实战 - Shell脚本编写最佳实践

Linux运维实战 - Shell脚本编写最佳实践

  • 2026-10-11 06:30:49
Linux运维实战 - Shell脚本编写最佳实践

🔧 Linux运维实战 - Shell脚本编写最佳实践

在企业IT运维中,Shell脚本是自动化部署、日志清理、系统监控的基石。然而,很多运维同学写的脚本“能跑就行”,忽视了健壮性、可维护性和安全性。今天,我们结合真实场景,分享几个经过生产环境验证的Shell脚本编写最佳实践。


一、脚本头部:规范与安全

每个脚本都应包含明确的元信息,并启用安全选项。

#!/bin/bash

#——————————————————————

# 脚本名称: cleanup_logs.sh

# 功能: 清理指定目录下超过7天的日志文件

# 作者: 运维组

# 版本: 1.0

# 更新日期: 2025-01-15

#——————————————————————

# 启用严格模式

set -euo pipefail

关键说明:

• set -e:遇到错误立即退出,避免连锁故障

• set -u:使用未定义变量时报错,防止拼写错误

• set -o pipefail:管道中任一命令失败,整体返回非0

• #!/bin/bash而非 /bin/sh,避免不同发行版差异


二、变量与参数:防御式编程

1. 变量赋值时**始终使用双引号**

# 错误写法:当$file包含空格时会分裂成多个参数

rm -rf $file

# 正确写法

rm -rf "$file"

2. 参数检查要有**明确反馈**

if [ $# -ne 2 ]; then

  echo "用法: $0 <日志目录> <保留天数>"

  exit 1

fi

LOG_DIR="$1"

RETENTION_DAYS="$2"

3. 使用 **readonly** 保护关键变量

readonly MAX_BACKUP_SIZE=1024   # 单位MB

readonly CONFIG_FILE="/etc/backup.conf"


三、函数封装:模块化与复用

避免“面条式脚本”,将逻辑拆分为职责单一的函数。

# 函数:检查目录是否存在且可写

check_directory() {

  local dir="$1"

  if [ ! -d "$dir" ]; then

    echo "错误:目录 $dir 不存在" >&2

    return 1

  fi

  if [ ! -w "$dir" ]; then

    echo "错误:目录 $dir 不可写" >&2

    return 1

  fi

  return 0

}

# 函数:安全删除文件(记录日志)

safe_remove() {

  local file="$1"

  if [ -f "$file" ]; then

    rm -f "$file" && echo "$(date) 已删除: $file" >> /var/log/cleanup.log

  fi

}

# 主逻辑

main() {

  check_directory "$LOG_DIR" || exit 1

  find "$LOG_DIR" -type f -name "*.log" -mtime +"$RETENTION_DAYS" | while read -r f; do

    safe_remove "$f"

  done

}

main "$@"


四、错误处理:日志与返回值

不要默默吞掉错误,给运维人员清晰的排查线索。

1. 统一错误输出到 stderr

log_error() {

  echo "$(date) [ERROR] $*" >&2

}

log_info() {

  echo "$(date) [INFO] $*"

}

2. 使用 trap 捕获异常信号

cleanup() {

  echo "收到中断信号,清理临时文件..."

  rm -rf /tmp/backup_$$

  exit 1

}

trap cleanup INT TERM

3. 命令执行结果检查

if ! tar czf backup.tar.gz /data; then

  log_error "备份打包失败"

  exit 2

fi


五、性能与安全:避免常见陷阱

⚠️ 避免使用 `ls` 解析文件名

# 错误:文件名含换行符会崩溃

for file in $(ls *.log); do ...

# 正确:使用 find 或通配符

for file in *.log; do

  [ -e "$file" ] || continue

  echo "处理: $file"

done

🔒 临时文件安全

# 使用 mktemp 创建唯一临时文件

TEMP_FILE=$(mktemp /tmp/script_XXXXXX)

trap "rm -f $TEMP_FILE" EXIT

📊 避免无意义的管道链

# 低效:cat + grep 多此一举

cat /var/log/syslog | grep "error"

# 高效:直接 grep

grep "error" /var/log/syslog


六、实战案例:日志归档脚本

完整示例,展示上述实践的综合应用:

#!/bin/bash

set -euo pipefail

readonly APP_NAME="webapp"

readonly LOG_DIR="/var/log/$APP_NAME"

readonly ARCHIVE_DIR="/backup/logs"

readonly RETENTION_DAYS=30

log_info() { echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] $*"; }

log_error() { echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] $*" >&2; }

archive_logs() {

  local date_str=$(date +%Y%m%d)

  local archive_file="${ARCHIVE_DIR}/${APP_NAME}_${date_str}.tar.gz"

  if [ ! -d "$ARCHIVE_DIR" ]; then

    mkdir -p "$ARCHIVE_DIR" || { log_error "无法创建归档目录"; exit 1; }

  fi

  # 归档前一天的日志(避免正在写入的文件)

  find "$LOG_DIR" -maxdepth 1 -name "*.log" -mtime +1 -print0 | \

    xargs -0 tar czf "$archive_file" || {

    log_error "归档失败"; return 1

  }

  # 删除已归档的原始文件

  find "$LOG_DIR" -maxdepth 1 -name "*.log" -mtime +1 -delete

  log_info "归档完成: $archive_file"

}

cleanup_old_archives() {

  find "$ARCHIVE_DIR" -name "*.tar.gz" -mtime +"$RETENTION_DAYS" -delete

  log_info "已清理 $RETENTION_DAYS 天前的归档"

}

main() {

  log_info "开始日志归档任务"

  archive_logs

  cleanup_old_archives

  log_info "任务完成"

}

main "$@"


总结

优秀的Shell脚本不是“能跑就行”,而是可读、可维护、可调试。牢记三条原则:变量加引号、函数拆逻辑、错误有反馈。掌握这些最佳实践,你的脚本将真正成为运维利器,而非定时炸弹。

最新文章

随机文章