背景

AI时代还是会用到shell脚本,作为技术人。
如果白天被同事问道某个技术点自己忘记了,一定会复习一下,复习的同时学一篇博客,完善一下之前的shell 模板吧~
如果是模板前两个基本够用。
如果其他场景 搜索关键字即可

脚本模板 - 简单版 - 平时用修改主函数即可

#!/usr/bin/env bash
set -euo pipefail  # 严格模式:错误退出、未定义变量报错、管道错误传播

# ====== 配置区 ======
SCRIPT_NAME=$(basename "$0")
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
LOG_FILE="${SCRIPT_DIR}/${SCRIPT_NAME%.*}.log"

# ====== 日志函数 ======
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# ====== 帮助信息 ======
show_help() {
    cat << EOF
${SCRIPT_NAME} - 日常运维脚本

用法: ${SCRIPT_NAME} [选项] [参数]

选项:
    -h           显示此帮助信息
    -f FILE      指定配置文件
    -d DIR       指定工作目录
    -v           详细输出模式
    -t           测试模式(不执行实际操作)

示例:
    ${SCRIPT_NAME} -f config.ini
    ${SCRIPT_NAME} -d /opt/app -v
EOF
    exit 0
}

# ====== 参数解析 ======
parse_args() {
    local work_dir=""
    local config_file=""
    local verbose=false
    local test_mode=false

    while getopts "hf:d:vt" opt; do
        case $opt in
            h) show_help ;;
            f) config_file="$OPTARG" ;;
            d) work_dir="$OPTARG" ;;
            v) verbose=true ;;
            t) test_mode=true ;;
            ?) show_help ;;
        esac
    done
    shift $((OPTIND-1))
    
    # 设置工作目录
    if [ -n "$work_dir" ]; then
        if [ -d "$work_dir" ]; then
            cd "$work_dir" || { log "错误:无法切换到目录 $work_dir"; exit 1; }
            log "切换到工作目录: $(pwd)"
        else
            log "错误:目录不存在 $work_dir"
            exit 1
        fi
    fi
    
    return 0
}

# ====== 主函数 ======
main() {
    log "脚本开始执行: ${SCRIPT_NAME}"
    
    # 解析参数
    parse_args "$@"
    
    # 业务逻辑示例
    log "执行系统检查..."
    
    # 1. 检查磁盘空间
    local disk_usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
    if [ "$disk_usage" -gt 80 ]; then
        log "警告:磁盘使用率 ${disk_usage}% > 80%"
    fi
    
    # 2. 检查内存
    local mem_free=$(free -m | awk '/Mem:/ {printf "%.1f", $4/$2*100}')
    log "可用内存百分比: ${mem_free}%"
    
    # 3. 进程检查(示例:检查Nginx)
    if pgrep nginx > /dev/null; then
        log "服务状态: Nginx 正在运行"
    else
        log "服务状态: Nginx 未运行"
    fi
    
    log "脚本执行完成"
}

# ====== 脚本入口 ======
if [[ "${BASH_SOURCE[0]}" = "${0}" ]]; then
    main "$@"
fi

使用示例:

# 查看帮助
./daily_ops.sh -h

# 切换到指定目录执行
./daily_ops.sh -d /var/log

# 详细模式
./daily_ops.sh -v

# 测试模式
./daily_ops.sh -t

# 组合使用
./daily_ops.sh -d /opt/app -f config.ini -v

脚本特点:

  1. 严格模式set -euo pipefail 确保安全性
  2. 自动日志:所有输出同时显示并记录到文件
  3. 目录切换:安全的目录切换和验证
  4. 完整帮助:标准的 -h 参数支持
  5. 参数解析:使用 getopts 处理选项
  6. 简洁实用:50行以内,覆盖日常运维基本需求

这个脚本包含了运维脚本中最常用的功能,可以直接用于日常的服务器检查、监控、维护等任务。

覆盖常见语法以及兼顾幂等性 通用脚本模板 - 加长版

关键点:
严格模式:set -euo pipefail 防止错误继续执行
锁机制:防止脚本重复执行
信号捕获:优雅处理中断信号
彩色日志:更好地区分日志级别
模块化函数:更清晰的代码结构
完善错误处理:每个函数都有返回值检查
配置分离:关键配置集中在顶部
依赖检查:确保运行环境正常
更安全的变量引用:使用双引号和花括号
这个模板覆盖了运维脚本的常见需求,可以直接用于实际工作。

#!/usr/bin/env bash
set -euo pipefail  # 添加:严格模式,遇到错误立即退出

# ====== 配置区域 ======
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
readonly LOG_DIR="${SCRIPT_DIR}/logs"
readonly LOCK_FILE="/tmp/${SCRIPT_NAME}.lock"

# 颜色定义
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color

# ====== 日志函数 ======
init_log() {
    mkdir -p "$LOG_DIR"
    local log_date=$(date '+%Y%m%d')
    LOG_FILE="${LOG_DIR}/${SCRIPT_NAME}.${log_date}.log"
}

log() {
    local level=$1
    local message=$2
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    case $level in
        "INFO") color=$GREEN ;;
        "WARN") color=$YELLOW ;;
        "ERROR") color=$RED ;;
        *) color=$NC ;;
    esac
    
    echo -e "${color}[${timestamp}] [${level}] ${message}${NC}"
    echo "[${timestamp}] [${level}] ${message}" >> "$LOG_FILE"
}

# ====== 锁机制(防止重复执行) ======
check_lock() {
    if [ -f "$LOCK_FILE" ]; then
        local pid=$(cat "$LOCK_FILE")
        if ps -p "$pid" > /dev/null 2>&1; then
            log "ERROR" "脚本已在运行 (PID: $pid)"
            exit 1
        else
            rm -f "$LOCK_FILE"
        fi
    fi
    echo $$ > "$LOCK_FILE"
}

cleanup() {
    rm -f "$LOCK_FILE"
    log "INFO" "脚本执行结束,清理完成"
}

# ====== 业务函数 ======
function check_dependencies() {
    local deps=("nginx" "redis-cli" "awk")
    for dep in "${deps[@]}"; do
        if ! command -v "$dep" &> /dev/null; then
            log "ERROR" "依赖命令不存在: $dep"
            return 1
        fi
    done
}

function backup_config() {
    local config_file="$1"
    local backup_dir="${SCRIPT_DIR}/backup"
    local timestamp=$(date '+%Y%m%d_%H%M%S')
    
    mkdir -p "$backup_dir"
    cp "$config_file" "${backup_dir}/$(basename "$config_file").${timestamp}.bak"
    
    if [ $? -eq 0 ]; then
        log "INFO" "配置文件备份成功: $config_file"
    else
        log "ERROR" "配置文件备份失败"
        return 1
    fi
}

function service_control() {
    local service_name="$1"
    local action="$2"
    
    if systemctl list-unit-files | grep -q "${service_name}.service"; then
        if systemctl "$action" "${service_name}.service"; then
            log "INFO" "服务 ${service_name} ${action} 成功"
            return 0
        else
            log "ERROR" "服务 ${service_name} ${action} 失败"
            return 1
        fi
    else
        log "WARN" "服务 ${service_name} 不存在"
        return 2
    fi
}

# ====== 主逻辑 ======
main() {
    trap cleanup EXIT INT TERM  # 设置信号捕获
    check_lock
    init_log
    
    log "INFO" "脚本开始执行"
    
    # 检查依赖
    check_dependencies || exit 1
    
    # 幂等性检查示例
    local target_dir="/etc/nginx/conf.d"
    if [ ! -d "$target_dir" ]; then
        log "ERROR" "目标目录不存在: $target_dir"
        exit 1
    fi
    
    # 备份原有配置(幂等性:重复执行不会产生问题)
    backup_config "/etc/nginx/nginx.conf"
    
    # 服务操作
    service_control "nginx" "restart"
    
    # 验证服务状态
    if systemctl is-active --quiet nginx.service; then
        log "INFO" "Nginx服务运行正常"
    else
        log "ERROR" "Nginx服务异常"
        exit 1
    fi
    
    log "INFO" "所有操作执行完成"
}

# ====== 参数解析 ======
parse_args() {
    while getopts "hu:p:s:" opt; do
        case $opt in
            h) help; exit 0 ;;
            u) username="$OPTARG" ;;
            p) password="$OPTARG" ;;
            s) 
                case $OPTARG in
                    "phy") echo "物理CPU数: $(get_phy_cpu)" ;;
                    "cores") echo "每个CPU核数: $(get_cores)" ;;
                    "pro") echo "逻辑CPU数: $(get_pro_cpu)" ;;
                    *) echo "无效参数"; help ;;
                esac
                exit 0
                ;;
            ?) help; exit 1 ;;
        esac
    done
}

help() {
    cat << EOF
${SCRIPT_NAME} - 运维部署脚本

用法: ${SCRIPT_NAME} [选项]

选项:
    -h          显示此帮助信息
    -u USER     指定用户名
    -p PASSWORD 指定密码
    -s TYPE     系统信息类型: phy/cores/pro

示例:
    ${SCRIPT_NAME} -s phy
    ${SCRIPT_NAME} -u admin -p password
EOF
}

# ====== 工具函数 ======
get_phy_cpu() {
    grep -c "physical id" /proc/cpuinfo | sort -u
}

get_cores() {
    grep "cpu cores" /proc/cpuinfo | uniq | awk -F: '{print $2}' | tr -d ' '
}

get_pro_cpu() {
    grep -c processor /proc/cpuinfo
}

# ====== 脚本入口 ======
if [[ $# -eq 0 ]]; then
    help
    exit 1
fi

parse_args "$@"
main

Shell脚本编程:从入门到高级运维实战指南

前言

在Linux运维的世界里,Shell脚本无疑是最高效的"瑞士军刀"。无论是日常的系统维护、批量操作,还是复杂的自动化部署,掌握Shell脚本都是每个运维工程师的必备技能。本文将带你从基础语法开始,逐步深入高级运维实战场景。

第一部分:Shell脚本基础篇

1.1 Shell脚本是什么?

Shell脚本本质上是一个文本文件,包含一系列Shell命令,可以被Shell解释器执行。它让重复性的系统管理工作变得自动化和标准化。

1.2 你的第一个Shell脚本

#!/bin/bash
# 这是一个注释
echo "Hello, World!"

保存为hello.sh,然后赋予执行权限:

chmod +x hello.sh
./hello.sh

1.3 变量和数据类型

#!/bin/bash
# 变量定义和使用
name="运维工程师"
age=30
PI=3.14159

echo "姓名: $name"
echo "年龄: ${age}岁"  # 花括号让变量边界更清晰

# 环境变量
echo "当前用户: $USER"
echo "家目录: $HOME"

1.4 基本控制结构

条件判断
#!/bin/bash
# if-elif-else结构
read -p "请输入分数(0-100): " score

if [ $score -ge 90 ]; then
    echo "优秀"
elif [ $score -ge 80 ]; then
    echo "良好"
elif [ $score -ge 60 ]; then
    echo "及格"
else
    echo "不及格"
fi

# case语句
read -p "请输入操作(start/stop/restart): " action

case $action in
    start)
        echo "开始服务"
        ;;
    stop)
        echo "停止服务"
        ;;
    restart)
        echo "重启服务"
        ;;
    *)
        echo "未知操作"
        exit 1
        ;;
esac
循环结构
#!/bin/bash
# for循环
echo "=== for循环示例 ==="
for i in {1..5}; do
    echo "第 $i 次循环"
done

# 遍历数组
services=("nginx" "mysql" "redis")
for service in "${services[@]}"; do
    echo "检查服务: $service"
done

# while循环
echo "=== while循环示例 ==="
count=1
while [ $count -le 5 ]; do
    echo "计数: $count"
    ((count++))
done

# 无限循环(常用于监控)
while true; do
    if ping -c 1 google.com &> /dev/null; then
        echo "网络正常 $(date)"
    else
        echo "网络异常 $(date)"
    fi
    sleep 10  # 等待10秒
done

1.5 函数基础

#!/bin/bash
# 函数定义和调用
function say_hello() {
    local name=$1  # local表示局部变量
    echo "Hello, $name!"
}

# 带返回值的函数
check_service() {
    local service_name=$1
    if systemctl is-active --quiet "$service_name"; then
        return 0  # 成功
    else
        return 1  # 失败
    fi
}

# 调用函数
say_hello "读者"
if check_service "nginx"; then
    echo "Nginx正在运行"
else
    echo "Nginx未运行"
fi

第二部分:Shell脚本进阶篇

2.1 参数处理和选项解析

#!/bin/bash
# getopts参数解析
usage() {
    echo "用法: $0 [-h] [-v] [-f file] [-u user]"
    echo "  -h        显示帮助"
    echo "  -v        详细输出"
    echo "  -f file   指定配置文件"
    echo "  -u user   指定用户名"
    exit 1
}

# 默认值
verbose=false
config_file=""
username=""

# 解析参数
while getopts "hvf:u:" opt; do
    case $opt in
        h) usage ;;
        v) verbose=true ;;
        f) config_file="$OPTARG" ;;
        u) username="$OPTARG" ;;
        ?) usage ;;
    esac
done

# 剩余参数
shift $((OPTIND-1))

if $verbose; then
    echo "详细模式已开启"
fi

if [ -n "$config_file" ]; then
    echo "配置文件: $config_file"
fi

if [ $# -gt 0 ]; then
    echo "额外参数: $@"
fi

2.2 文件操作和文本处理

#!/bin/bash
# 文件操作示例
check_and_backup() {
    local source_file=$1
    local backup_dir="/backup"
    
    # 检查文件是否存在
    if [ ! -f "$source_file" ]; then
        echo "错误: 文件不存在 - $source_file" >&2
        return 1
    fi
    
    # 检查备份目录
    mkdir -p "$backup_dir"
    
    # 创建带时间戳的备份
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local backup_file="${backup_dir}/$(basename $source_file).${timestamp}.bak"
    
    cp "$source_file" "$backup_file"
    
    # 验证备份
    if cmp -s "$source_file" "$backup_file"; then
        echo "备份成功: $backup_file"
        return 0
    else
        echo "备份失败" >&2
        return 1
    fi
}

# 文本处理示例
process_log() {
    local log_file=$1
    local output_file=$2
    
    # 提取错误日志并统计
    echo "=== 错误日志分析 ===" > "$output_file"
    echo "时间范围: $(date)" >> "$output_file"
    echo "==================" >> "$output_file"
    
    # 统计不同错误类型
    grep -i "error" "$log_file" | \
        awk '{count[$4]++} END {for (type in count) print type ": " count[type]}' \
        >> "$output_file"
        
    # 提取最近10条错误
    echo -e "\n=== 最近10条错误 ===" >> "$output_file"
    grep -i "error" "$log_file" | tail -10 >> "$output_file"
}

2.3 错误处理和调试

#!/bin/bash
# 启用严格模式
set -euo pipefail

# 错误处理函数
error_exit() {
    echo "错误: $1" >&2
    exit 1
}

# 清理函数
cleanup() {
    echo "执行清理操作..."
    # 删除临时文件
    rm -f /tmp/temp_*.log
    # 停止相关进程等
    echo "清理完成"
}

# 注册清理函数
trap cleanup EXIT INT TERM

# 调试函数
debug() {
    if [ "${DEBUG:-false}" = "true" ]; then
        echo "[DEBUG] $1" >&2
    fi
}

# 示例:安全执行命令
safe_exec() {
    local cmd="$@"
    debug "执行命令: $cmd"
    
    if eval "$cmd"; then
        debug "命令执行成功"
        return 0
    else
        error_exit "命令执行失败: $cmd"
    fi
}

# 使用示例
main() {
    debug "开始主函数"
    
    # 必须存在的目录检查
    local required_dirs=("/etc/nginx" "/var/log")
    for dir in "${required_dirs[@]}"; do
        if [ ! -d "$dir" ]; then
            error_exit "目录不存在: $dir"
        fi
    done
    
    # 安全执行命令
    safe_exec "ls -la /etc/nginx"
    
    debug "主函数结束"
}

# 设置调试模式
export DEBUG=true
main

第三部分:运维实战场景

3.1 系统监控和告警

#!/bin/bash
# 系统资源监控脚本
set -euo pipefail

# 配置
readonly THRESHOLD_CPU=80
readonly THRESHOLD_MEM=85
readonly THRESHOLD_DISK=90
readonly ALERT_EMAIL="admin@example.com"
readonly LOG_FILE="/var/log/system_monitor.log"

# 初始化日志
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 检查CPU使用率
check_cpu() {
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
    cpu_usage=${cpu_usage%.*}  # 取整
    
    if [ "$cpu_usage" -gt "$THRESHOLD_CPU" ]; then
        log "警告: CPU使用率过高 - ${cpu_usage}%"
        send_alert "CPU" "$cpu_usage"
        return 1
    fi
    return 0
}

# 检查内存使用率
check_memory() {
    local mem_total=$(free -m | awk '/Mem:/ {print $2}')
    local mem_used=$(free -m | awk '/Mem:/ {print $3}')
    local mem_percent=$((mem_used * 100 / mem_total))
    
    if [ "$mem_percent" -gt "$THRESHOLD_MEM" ]; then
        log "警告: 内存使用率过高 - ${mem_percent}%"
        send_alert "内存" "$mem_percent"
        return 1
    fi
    return 0
}

# 检查磁盘使用率
check_disk() {
    while IFS= read -r line; do
        local usage=$(echo "$line" | awk '{print $5}' | sed 's/%//')
        local partition=$(echo "$line" | awk '{print $6}')
        
        if [ "$usage" -gt "$THRESHOLD_DISK" ]; then
            log "警告: 磁盘分区 $partition 使用率过高 - ${usage}%"
            send_alert "磁盘($partition)" "$usage"
        fi
    done < <(df -h | grep '^/dev/')
}

# 检查服务状态
check_services() {
    local services=("nginx" "mysql" "redis" "docker")
    
    for service in "${services[@]}"; do
        if systemctl is-active --quiet "$service"; then
            log "服务正常: $service"
        else
            log "错误: 服务 $service 未运行"
            send_alert "服务" "$service 停止"
            
            # 尝试重启
            log "尝试重启服务: $service"
            if systemctl restart "$service"; then
                log "服务重启成功: $service"
            else
                log "服务重启失败: $service"
            fi
        fi
    done
}

# 发送告警
send_alert() {
    local resource=$1
    local value=$2
    local subject="[系统告警] ${resource} 异常"
    local body="资源: ${resource}\n当前值: ${value}%\n服务器: $(hostname)\n时间: $(date)"
    
    echo -e "$body" | mail -s "$subject" "$ALERT_EMAIL"
}

# 生成报告
generate_report() {
    echo "=== 系统监控报告 $(date) ==="
    echo "主机名: $(hostname)"
    echo "运行时间: $(uptime -p)"
    echo ""
    echo "CPU信息:"
    top -bn1 | grep "Cpu(s)"
    echo ""
    echo "内存信息:"
    free -h
    echo ""
    echo "磁盘信息:"
    df -h
    echo ""
    echo "最近5个高进程:"
    ps aux --sort=-%cpu | head -6
}

# 主函数
main() {
    log "开始系统监控检查"
    
    check_cpu
    check_memory
    check_disk
    check_services
    
    # 每小时生成一次详细报告
    if [ "$(date +%M)" = "00" ]; then
        generate_report >> "$LOG_FILE"
    fi
    
    log "系统监控检查完成"
}

# 设置定时任务运行
main

3.2 自动化部署脚本

#!/bin/bash
# 应用自动化部署脚本
set -euo pipefail

# 配置
readonly APP_NAME="myapp"
readonly APP_USER="appuser"
readonly DEPLOY_DIR="/opt/${APP_NAME}"
readonly BACKUP_DIR="/backup/${APP_NAME}"
readonly LOG_FILE="/var/log/deploy_${APP_NAME}.log"
readonly GIT_REPO="https://github.com/example/myapp.git"
readonly BRANCH="main"

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

log() {
    local level=$1
    local msg=$2
    
    case $level in
        "INFO") color=$GREEN ;;
        "ERROR") color=$RED ;;
        "WARN") color=$YELLOW ;;
        *) color=$NC ;;
    esac
    
    echo -e "${color}[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $msg${NC}"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $msg" >> "$LOG_FILE"
}

# 检查前置条件
check_prerequisites() {
    log "INFO" "检查部署前置条件"
    
    # 检查用户是否存在
    if ! id "$APP_USER" &>/dev/null; then
        log "ERROR" "用户 $APP_USER 不存在"
        return 1
    fi
    
    # 检查必要命令
    local required_cmds=("git" "tar" "systemctl" "python3")
    for cmd in "${required_cmds[@]}"; do
        if ! command -v "$cmd" &>/dev/null; then
            log "ERROR" "命令不存在: $cmd"
            return 1
        fi
    done
    
    # 检查目录权限
    local required_dirs=("/opt" "/backup")
    for dir in "${required_dirs[@]}"; do
        if [ ! -w "$dir" ]; then
            log "ERROR" "目录不可写: $dir"
            return 1
        fi
    done
    
    log "INFO" "前置条件检查通过"
}

# 备份当前版本
backup_current() {
    log "INFO" "开始备份当前版本"
    
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local backup_path="${BACKUP_DIR}/${timestamp}"
    
    mkdir -p "$backup_path"
    
    if [ -d "$DEPLOY_DIR" ]; then
        cp -r "$DEPLOY_DIR"/* "$backup_path/"
        log "INFO" "备份完成: $backup_path"
        
        # 清理旧备份(保留最近7天)
        find "$BACKUP_DIR" -type d -mtime +7 -exec rm -rf {} \;
    else
        log "WARN" "部署目录不存在,跳过备份"
    fi
}

# 拉取代码
pull_code() {
    log "INFO" "开始拉取代码"
    
    if [ -d "$DEPLOY_DIR/.git" ]; then
        cd "$DEPLOY_DIR"
        git fetch origin
        git checkout "$BRANCH"
        git reset --hard "origin/$BRANCH"
    else
        git clone -b "$BRANCH" "$GIT_REPO" "$DEPLOY_DIR"
    fi
    
    # 记录提交信息
    cd "$DEPLOY_DIR"
    local commit_hash=$(git rev-parse --short HEAD)
    local commit_msg=$(git log -1 --pretty=%B)
    
    log "INFO" "代码拉取完成"
    log "INFO" "提交哈希: $commit_hash"
    log "INFO" "提交信息: $commit_msg"
    
    echo "$commit_hash" > "${DEPLOY_DIR}/REVISION"
}

# 安装依赖
install_dependencies() {
    log "INFO" "安装依赖"
    
    cd "$DEPLOY_DIR"
    
    # Python依赖
    if [ -f "requirements.txt" ]; then
        pip3 install -r requirements.txt
    fi
    
    # NPM依赖
    if [ -f "package.json" ]; then
        npm install --production
    fi
    
    # 设置文件权限
    chown -R "${APP_USER}:${APP_USER}" "$DEPLOY_DIR"
    find "$DEPLOY_DIR" -type f -name "*.sh" -exec chmod +x {} \;
}

# 数据库迁移
run_migrations() {
    log "INFO" "执行数据库迁移"
    
    if [ -f "${DEPLOY_DIR}/manage.py" ]; then
        cd "$DEPLOY_DIR"
        sudo -u "$APP_USER" python3 manage.py migrate --noinput
    fi
    
    log "INFO" "数据库迁移完成"
}

# 重启服务
restart_service() {
    log "INFO" "重启应用服务"
    
    local service_name="${APP_NAME}.service"
    
    if systemctl is-enabled "$service_name" &>/dev/null; then
        systemctl restart "$service_name"
        sleep 5
        
        if systemctl is-active --quiet "$service_name"; then
            log "INFO" "服务重启成功"
        else
            log "ERROR" "服务重启失败"
            return 1
        fi
    else
        log "WARN" "服务未启用,跳过重启"
    fi
}

# 健康检查
health_check() {
    log "INFO" "开始健康检查"
    
    local max_attempts=10
    local attempt=1
    local health_url="http://localhost:8080/health"
    
    while [ $attempt -le $max_attempts ]; do
        if curl -s -f "$health_url" &>/dev/null; then
            log "INFO" "应用健康检查通过"
            return 0
        fi
        
        log "WARN" "健康检查失败,尝试 $attempt/$max_attempts"
        sleep 10
        ((attempt++))
    done
    
    log "ERROR" "健康检查失败,达到最大重试次数"
    return 1
}

# 回滚操作
rollback() {
    log "ERROR" "开始回滚操作"
    
    # 找到最新的备份
    local latest_backup=$(find "$BACKUP_DIR" -type d -name "20*" | sort -r | head -1)
    
    if [ -d "$latest_backup" ]; then
        log "INFO" "回滚到备份: $latest_backup"
        
        # 清空当前目录
        rm -rf "${DEPLOY_DIR:?}/*"
        
        # 恢复备份
        cp -r "$latest_backup"/* "$DEPLOY_DIR/"
        
        # 重启服务
        restart_service
        
        log "INFO" "回滚完成"
    else
        log "ERROR" "找不到可用备份"
        return 1
    fi
}

# 主部署流程
deploy() {
    log "INFO" "开始部署 $APP_NAME"
    
    # 检查前置条件
    if ! check_prerequisites; then
        exit 1
    fi
    
    # 执行部署步骤
    local steps=(
        "backup_current"
        "pull_code"
        "install_dependencies"
        "run_migrations"
        "restart_service"
        "health_check"
    )
    
    for step in "${steps[@]}"; do
        log "INFO" "执行步骤: $step"
        
        if ! $step; then
            log "ERROR" "步骤 $step 执行失败"
            
            # 尝试回滚
            if rollback; then
                log "INFO" "回滚成功"
            else
                log "ERROR" "回滚失败"
            fi
            
            exit 1
        fi
    done
    
    log "INFO" "部署完成!"
    
    # 发送部署通知
    send_notification "success" "应用 $APP_NAME 部署成功"
}

# 发送通知
send_notification() {
    local status=$1
    local message=$2
    
    # 可以集成到各种通知系统
    # Slack、钉钉、邮件、企业微信等
    echo "发送部署通知: $status - $message"
    
    # 示例:发送邮件
    # echo "$message" | mail -s "部署通知: $APP_NAME $status" "team@example.com"
}

# 使用说明
usage() {
    cat << EOF
Usage: $0 [command]

Commands:
    deploy      执行部署
    rollback    回滚到上一个版本
    status      查看部署状态
    help        显示此帮助信息

Examples:
    $0 deploy
    $0 rollback
    $0 status
EOF
}

# 命令行参数处理
main() {
    local command=${1:-help}
    
    case "$command" in
        deploy)
            deploy
            ;;
        rollback)
            rollback
            ;;
        status)
            echo "当前版本: $(cat ${DEPLOY_DIR}/REVISION 2>/dev/null || echo '未知')"
            systemctl status "${APP_NAME}.service" || true
            ;;
        help)
            usage
            ;;
        *)
            echo "未知命令: $command"
            usage
            exit 1
            ;;
    esac
}

# 脚本入口
if [[ $# -gt 0 ]]; then
    main "$@"
else
    usage
fi

3.3 日志分析和报告生成

#!/bin/bash
# 日志分析脚本
set -euo pipefail

analyze_nginx_logs() {
    local log_file=$1
    local report_file=$2
    
    echo "=== Nginx访问日志分析报告 ===" > "$report_file"
    echo "分析时间: $(date)" >> "$report_file"
    echo "日志文件: $log_file" >> "$report_file"
    echo "=================================" >> "$report_file"
    
    # 总请求数
    local total_requests=$(wc -l < "$log_file")
    echo "总请求数: $total_requests" >> "$report_file"
    
    # 请求方法统计
    echo -e "\n--- 请求方法统计 ---" >> "$report_file"
    awk '{print $6}' "$log_file" | \
        sed 's/"//g' | \
        sort | uniq -c | sort -rn >> "$report_file"
    
    # 状态码统计
    echo -e "\n--- HTTP状态码统计 ---" >> "$report_file"
    awk '{print $9}' "$log_file" | \
        sort | uniq -c | sort -rn >> "$report_file"
    
    # 最频繁的IP
    echo -e "\n--- 最频繁访问IP (Top 10) ---" >> "$report_file"
    awk '{print $1}' "$log_file" | \
        sort | uniq -c | sort -rn | head -10 >> "$report_file"
    
    # 最受欢迎的URL
    echo -e "\n--- 最受欢迎URL (Top 10) ---" >> "$report_file"
    awk '{print $7}' "$log_file" | \
        sort | uniq -c | sort -rn | head -10 >> "$report_file"
    
    # 错误请求分析
    echo -e "\n--- 错误请求分析 (4xx/5xx) ---" >> "$report_file"
    awk '$9 ~ /^[45]/ {print $9, $7, $1}' "$log_file" | \
        head -20 >> "$report_file"
    
    # 流量统计
    echo -e "\n--- 流量统计 ---" >> "$report_file"
    awk '{sum += $10} END {printf "总流量: %.2f MB\n", sum/1024/1024}' \
        "$log_file" >> "$report_file"
    
    echo -e "\n报告生成完成: $report_file"
}

第四部分:最佳实践和高级技巧

4.1 Shell脚本最佳实践

  1. 启用严格模式

    set -euo pipefail
    
  2. 使用函数和模块化

    • 每个函数只做一件事
    • 函数长度不超过50行
  3. 完善的错误处理

    trap 'cleanup; exit 1' ERR
    
  4. 可读性和可维护性

    • 使用有意义的变量名
    • 添加注释和文档
    • 统一的代码风格
  5. 安全性考虑

    # 避免使用eval
    # 验证用户输入
    # 最小权限原则
    

4.2 性能优化技巧

  1. 减少子进程创建

    # 不好:每次循环都创建新进程
    for file in *.txt; do
        wc -l "$file"
    done
    
    # 好:一次性处理
    wc -l *.txt
    
  2. 使用内置命令

    # 使用shell内置而非外部命令
    # 内置: echo, printf, test, [, read
    # 外部: cat, grep, sed, awk
    
  3. 避免不必要的管道

    # 不好:多个管道
    cat file | grep pattern | awk '{print $1}'
    
    # 好:单个命令完成
    awk '/pattern/ {print $1}' file
    

4.3 调试技巧

# 调试模式运行
bash -x script.sh

# 在脚本中开启调试
set -x  # 开启调试
# 你的代码
set +x  # 关闭调试

# 只调试特定部分
echo "调试前"
(
    set -x
    # 需要调试的代码
    command1
    command2
)
echo "调试后"

结语

Shell脚本是运维工程师的强大工具,掌握它能够极大提高工作效率。从简单的自动化任务到复杂的部署流程,良好的Shell脚本设计都能让运维工作更加规范、高效。

记住:好的脚本应该是自文档化的、健壮的、可维护的。每次写脚本时,都要考虑到未来的维护者可能就是你自己。


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐