🐧 第05课:Shell脚本编程

📚 核心概念

🎯 Shell脚本:DevOps的瑞士军刀

Shell脚本是自动化运维的核心工具。从简单的备份脚本到复杂的部署流水线,Shell无处不在。一个优秀的DevOps工程师必须精通Shell脚本编程。

📜 Shell脚本执行流程

  .sh文件 → 读取 → 词法分析 → 语法解析 → 执行
    │                                    │
    │     ┌────────────────────────────┐ │
    └────▶│ #!/bin/bash (Shebang)     │ │
          │ 变量定义                   │ │
          │ 函数定义                   │ │
          │ 主逻辑                     │ │
          │ 条件判断/循环              │ │
          │ 命令执行                   │◀┘
          │ 返回退出码                 │
          └──────────────────────────┘
                 │
           0 = 成功
           非0 = 失败

📝 Bash脚本最佳实践

⌨️ 命令实操

⌨️ 实操命令

1. 基础脚本模板 ✅

脚本模板
#!/usr/bin/env bash
# deploy.sh - 应用部署脚本
# 作者: DevOps Team
# 用法: ./deploy.sh [环境] [版本]

set -euo pipefail

# ============ 配置 ============
readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
readonly APP_NAME="myapp"
readonly LOG_FILE="$SCRIPT_DIR/deploy.log"

# ============ 颜色输出 ============
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

log()   {{ echo -e "${{GREEN}}[INFO]${{NC}} $(date '+%Y-%m-%d %H:%M:%S') $*"; }}
warn()  {{ echo -e "${{YELLOW}}[WARN]${{NC}} $(date '+%Y-%m-%d %H:%M:%S') $*"; }}
error() {{ echo -e "${{RED}}[ERROR]${{NC}} $(date '+%Y-%m-%d %H:%M:%S') $*" >&2; exit 1; }}

# ============ 参数解析 ============
ENV="${{1:-staging}}"
VERSION="$${{2:-latest}}"

# ============ 前置检查 ============
check_prerequisites() {{
    log "检查前置条件..."
    command -v docker >/dev/null 2>&1 || error "需要docker"
    command -v kubectl >/dev/null 2>&1 || error "需要kubectl"
    [[ -f "$SCRIPT_DIR/config/$ENV.yaml" ]] || error "缺少配置: $ENV.yaml"
    log "前置条件检查通过 ✅"
}}

# ============ 部署函数 ============
deploy() {{
    log "开始部署 $APP_NAME v$VERSION 到 $ENV"
    # 部署逻辑...
    log "部署完成 ✅"
}}

# ============ 主流程 ============
main() {{
    log "========== 部署启动 =========="
    check_prerequisites
    deploy
    log "========== 部署结束 =========="
}}

main "$@"

2. 条件判断与循环 ✅

流程控制
# 条件判断
if [[ "$ENV" == "prod" ]]; then
    REPLICAS=3
    MEMORY="1Gi"
elif [[ "$ENV" == "staging" ]]; then
    REPLICAS=1
    MEMORY="512Mi"
else
    REPLICAS=1
    MEMORY="256Mi"
fi

# 文件判断
[[ -f "$CONFIG" ]]  && echo "文件存在"
[[ -d "$DIR" ]]     && echo "目录存在"
[[ -r "$FILE" ]]    && echo "可读"
[[ -x "$SCRIPT" ]]  && echo "可执行"
[[ -s "$FILE" ]]    && echo "文件非空"

# for循环
for host in web1 web2 web3; do
    ssh "$host" "sudo systemctl restart nginx"
done

# while循环(逐行读取)
while IFS= read -r line; do
    echo "处理: $line"
done < server-list.txt

# 并行执行
for host in web1 web2 web3; do
    ssh "$host" "uptime" &
done
wait  # 等待所有后台任务完成
echo "所有服务器检查完成"

3. 文本处理三剑客 ✅

grep/sed/awk
# grep - 文本搜索
grep -r "ERROR" /var/log/          # 递归搜索
grep -E "ERROR|WARN|FATAL" app.log # 扩展正则
grep -c "200" access.log           # 匹配行数
grep -A5 -B2 "Exception" app.log   # 上下文

# sed - 流编辑器
sed -i 's/oldhost/newhost/g' /etc/hosts    # 替换
sed -n '10,20p' file.txt                    # 打印行范围
sed '/^#/d' config.cfg                      # 删除注释行
sed -i "s/VERSION=.*/VERSION=$NEW_VER/" .env

# awk - 文本分析
awk '{{print $1, $4}}' access.log                    # 提取列
awk '$9 == "500" {{count++}} END {{print count}}' access.log  # 统计500
awk '{{sum+=$10}} END {{print "Total:", sum}}' data.txt
awk -F: '{{print $1}}' /etc/passwd                   # 指定分隔符

# 实战:分析Nginx访问日志
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 统计Top10 IP访问量

4. 自动化运维脚本 ✅

自动化脚本
#!/usr/bin/env bash
# auto-backup.sh - 自动备份脚本
set -euo pipefail

readonly BACKUP_DIR="/data/backups"
readonly RETENTION_DAYS=30
readonly TIMESTAMP=$(date +%Y%m%d_%H%M%S)

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

# 创建备份目录
mkdir -p "$BACKUP_DIR"/{{mysql,redis,config}}

# 备份MySQL
backup_mysql() {{
    log "开始备份MySQL..."
    mysqldump -u backup -p"$(cat /run/secrets/mysql_pass)" \
        --all-databases --single-transaction \
        | gzip > "$BACKUP_DIR/mysql/all_$TIMESTAMP.sql.gz"
    log "MySQL备份完成 ✅"
}}

# 备份Redis
backup_redis() {{
    log "开始备份Redis..."
    redis-cli BGSAVE
    sleep 5
    cp /var/lib/redis/dump.rdb "$BACKUP_DIR/redis/dump_$TIMESTAMP.rdb"
    log "Redis备份完成 ✅"
}}

# 备份配置文件
backup_configs() {{
    log "开始备份配置..."
    tar czf "$BACKUP_DIR/config/configs_$TIMESTAMP.tar.gz" \
        /etc/nginx/ /etc/ssh/ /opt/app/config/
    log "配置备份完成 ✅"
}}

# 清理过期备份
cleanup() {{
    log "清理${{RETENTION_DAYS}}天前的备份..."
    find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete
    log "清理完成 ✅"
}}

# 执行
backup_mysql
backup_redis
backup_configs
cleanup
log "========== 全部备份完成 =========="

📐 架构图

📐 架构图:Shell脚本在DevOps中的位置

  开发提交代码
       │
       ▼
  ┌──────────┐    Shell脚本
  │ CI/CD    │◄──────────────┐
  │ Pipeline │               │
  └────┬─────┘               │
       │ 触发                │ 自动化
       ▼                     │
  ┌──────────┐    ┌──────┐   │
  │ 构建脚本 │    │ 部署 │   │
  │ build.sh │    │脚本  │───┘
  └────┬─────┘    │deploy│
       │          │.sh   │
       ▼          └──┬───┘
  ┌──────────┐       │
  │ 测试脚本 │       ▼
  │ test.sh  │   ┌──────┐
  └────┬─────┘   │ 服务器│
       │          │ 集群  │
       ▼          └──────┘
  ┌──────────┐       ▲
  │ 监控脚本 │       │
  │ monitor  │───────┘
  │ .sh      │  健康检查
  └──────────┘

🔧 故障排查

🔧 故障排查

❌ 问题1:脚本执行报"Permission denied"

# 原因:脚本没有执行权限
chmod +x deploy.sh

# 或者用bash显式执行
bash deploy.sh

# 检查shebang行
head -1 deploy.sh
#!/usr/bin/env bash   # 推荐写法

# 如果是Windows换行符问题
file deploy.sh
# deploy.sh: ASCII text, with CRLF line terminators
sed -i 's/\r$//' deploy.sh   # 修复

❌ 问题2:变量为空导致意外行为

# 危险写法
rm -rf $DIR/*   # 如果DIR为空,变成 rm -rf /* !!!

# 安全写法
rm -rf "$${{DIR:?未设置DIR变量}}"/*

# 使用set -u防止未定义变量
set -u
echo $UNDEFINED_VAR  # 报错退出而不是输出空

# 默认值
DB_HOST="$${{DB_HOST:-localhost}}"
DB_PORT="$${{DB_PORT:-5432}}"

🏆 成就解锁:脚本大师!

掌握Shell脚本编程、文本处理三剑客、自动化运维脚本编写——这是DevOps自动化的基石。