Shell脚本运维知识

一、Shell脚本基础

1. 脚本开头

所有Shell脚本首行必须指定解释器,否则默认使用系统默认Shell(通常是bash),常用两种写法:

#!/bin/bash # 最常用,指定bash解释器(推荐)
#!/bin/sh # 兼容式写法,sh是bash的简化版,部分系统中与bash等价

补充:脚本执行前需添加执行权限:chmod +x 脚本名.sh,执行方式:./脚本名.shbash 脚本名.sh(无需执行权限)。

2. 注释语法

Shell脚本中,单行注释用 #(开头加空格更规范),多行注释可使用 <<EOF ... EOF 包裹(EOF可替换为任意字符)

3. 变量定义与使用

运维脚本中变量用于存储路径、参数、结果等,核心规则:等号两边无空格,使用时加$符号。

bash 复制代码
# 1. 基础变量定义(字符串、数字均可)
name="nginx"
port=80
path="/var/log/nginx"

# 2. 变量使用($变量名 或 ${变量名},推荐后者,避免歧义)
echo "服务名:$name"
echo "端口:${port}"
echo "日志路径:${path}/error.log"

# 3. 系统环境变量(可直接使用,常用)
echo "当前用户:$USER"       # 登录用户
echo "当前目录:$PWD"       # 当前工作目录
echo "系统路径:$PATH"       # 命令搜索路径
echo "脚本名:$0"           # 脚本自身文件名
echo "参数1:$1"            # 脚本接收的第一个参数($2-$9对应第2-9个参数)
echo "参数总数:$#"         # 脚本接收的参数个数
echo "所有参数:$*"         # 所有参数(作为一个整体)
echo "所有参数:$@"         # 所有参数(每个参数独立)

4.输入输出

bash 复制代码
# 1. 输出(echo)
echo "正常输出"              # 标准输出到终端
echo "错误信息" 1>&2        # 输出到错误流(终端也能看到,便于日志区分)
echo "不换行输出" | tr -d '\n'  # 取消换行

# 2. 输入(read,用于交互获取参数)
read -p "请输入服务名:" service  # 提示用户输入,将输入内容赋值给service变量
read -t 10 -p "请输入密码(10秒超时):" pwd  # -t 超时时间(秒)

# 3. 重定向(核心,用于日志记录)
echo "脚本执行时间:$(date)" > 1.log  # > 覆盖写入日志
echo "执行结果:成功" >> 1.log       # >> 追加写入日志
./test.sh > test.log 2>&1            # 所有输出(标准+错误)都写入日志(运维必用)

二、Shell脚本流程控制

运维脚本中最常用的3种流程控制:条件判断、循环、分支,重点掌握语法简洁、不易出错的写法。

1. 条件判断(if-else)

核心用于:判断服务是否运行、文件是否存在、参数是否传入等,语法:if 条件; then ... fi(分号可替换为换行)

bash 复制代码
# 示例1:判断服务是否运行(以nginx为例)
if ps -ef | grep -v grep | grep -q nginx; then
    echo "nginx服务正在运行"
else
    echo "nginx服务未运行,正在重启..."
    systemctl restart nginx
fi

# 示例2:判断文件是否存在(日志文件)
log_path="/var/log/nginx/error.log"
if [ -f "$log_path" ]; then  # -f 判断是否为普通文件
    echo "日志文件存在,大小:$(du -sh $log_path)"
elif [ -d "$log_path" ]; then  # -d 判断是否为目录
    echo "路径是目录,不是日志文件"
else
    echo "日志文件不存在,创建中..."
    touch $log_path
fi

# 常用判断条件(必记)
# -f :文件存在且是普通文件
# -d :目录存在
# -e :文件/目录存在(不区分类型)
# -r :文件可读
# -w :文件可写
# -x :文件可执行
# -z :字符串为空
# -n :字符串非空
# $a -eq $b :a等于b(数字比较)
# $a -gt $b :a大于b
# $a -lt $b :a小于b

2. 循环(for、while)

用于批量处理:批量启动服务、批量删除文件、批量检查主机存活等。

bash 复制代码
# 示例1:for循环(批量处理文件,删除7天前的日志)
log_dir="/var/log/nginx"
for log_file in $(ls $log_dir/*.log); do
    # 判断文件是否超过7天
    if [ $(find $log_file -mtime +7) ]; then
        rm -f $log_file
        echo "删除过期日志:$log_file"
    fi
done

# 示例2:for循环(批量检查主机存活,ping测试)
for ip in 192.168.1.{1..10}; do  # 批量生成IP(1-10)
    ping -c 2 -W 1 $ip > /dev/null 2>&1  # ping 2次,超时1秒,不输出结果
    if [ $? -eq 0 ]; then  # $? 是上一条命令的返回值(0=成功,非0=失败)
        echo "$ip 主机存活"
    else
        echo "$ip 主机宕机"
    fi
done

# 示例3:while循环(持续监控服务,异常重启)
while true; do  # 无限循环
    if ! ps -ef | grep -v grep | grep -q nginx; then
        systemctl restart nginx
        echo "$(date):nginx异常,已重启" >> /var/log/nginx/monitor.log
    fi
    sleep 60  # 每隔60秒检查一次
done

3. 分支(case)

用于多条件分支,比如脚本接收不同参数,执行不同操作(如启动、停止、重启服务)。

bash 复制代码
# 示例:服务管理脚本(接收start/stop/restart参数)
if [ $# -ne 1 ]; then  # 判断参数个数是否为1
    echo "用法:$0 {start|stop|restart}"
    exit 1  # 退出脚本,返回非0状态(表示执行失败)
fi

case $1 in
    start)
        systemctl start nginx
        echo "nginx启动成功"
        ;;  # 分支结束
    stop)
        systemctl stop nginx
        echo "nginx停止成功"
        ;;
    restart)
        systemctl restart nginx
        echo "nginx重启成功"
        ;;
    *)  # 匹配所有未命中的参数
        echo "错误:参数错误,仅支持 start|stop|restart"
        exit 1
        ;;
esac

三、运维常用Shell脚本

1. 服务监控脚本(nginx为例,异常重启+日志记录)

bash 复制代码
#!/bin/bash
# 脚本功能:监控nginx服务,异常自动重启,记录监控日志
# 作者:运维工程师
# 日期:2026-05-09

service="nginx"
log_path="/var/log/monitor_${service}.log"
# 写入脚本启动日志
echo "=====================================" >> $log_path
echo "监控脚本启动时间:$(date +'%Y-%m-%d %H:%M:%S')" >> $log_path

# 循环监控
while true; do
    # 判断服务是否运行
    if ! ps -ef | grep -v grep | grep -q $service; then
        # 服务异常,重启
        systemctl restart $service
        # 记录重启日志
        echo "$(date +'%Y-%m-%d %H:%M:%S'):${service}服务异常,已执行重启" >> $log_path
    fi
    # 每隔30秒检查一次
    sleep 30
done

2. 日志清理脚本(删除指定目录下过期日志,防止磁盘爆满)

bash 复制代码
#!/bin/bash
# 脚本功能:清理指定目录下7天前的日志文件,支持多目录
# 适用场景:/var/log、应用日志目录等

# 定义需要清理的日志目录(可添加多个)
log_dirs=("/var/log" "/var/log/nginx" "/var/log/mysqld")
# 定义过期时间(7天)
expire_days=7

# 循环清理每个目录
for dir in "${log_dirs[@]}"; do
    # 判断目录是否存在
    if [ ! -d "$dir" ]; then
        echo "$(date):目录 $dir 不存在,跳过" >> /var/log/log_clean.log
        continue
    fi
    # 删除过期日志(仅删除.log和.log-*后缀的文件)
    find $dir -name "*.log" -o -name "*.log-*" -mtime +$expire_days -delete
    echo "$(date):清理目录 $dir 下 ${expire_days}天前的日志完成" >> /var/log/log_clean.log
done

3. 磁盘空间监控脚本(满了自动报警,邮件/终端提示)

bash 复制代码
#!/bin/bash
# 脚本功能:监控所有磁盘分区,使用率超过80%报警
# 报警方式:终端提示+日志记录(可扩展邮件报警)

# 定义报警阈值(80%,可根据需求修改)
threshold=80

# 遍历所有磁盘分区(排除tmpfs等虚拟文件系统,避免误监控)
# 核心解析:while read usage mount 用法详解
df -hl | grep -v tmpfs | grep -v squashfs | awk '{print $5,$6}' | while read usage mount; do
    # 1. while read usage mount 核心作用:
    # 读取管道前一条命令(awk输出)的每一行内容,将每行拆分为两个变量
    # usage:接收awk输出的第1列(磁盘使用率,如85%)
    # mount:接收awk输出的第2列(磁盘挂载点,如/、/data)
    # 相当于"逐行读取、拆分变量",实现批量遍历每个磁盘分区
    
    # 提取使用率数字(去掉%符号,将字符串转为数字,用于比较)
    usage_num=${usage%\%}
    
    # 2. 关键注意点(避坑):
    # - read 会自动按空格拆分每行内容,若挂载点包含空格(如"/mnt/data 1"),会拆分失败
    # - 解决方案:awk输出时用特殊分隔符(如|),read时指定分隔符,示例:
    # df -hl | awk '{print $5"|"$6}' | while IFS="|" read usage mount; do
    
    # 判断是否超过阈值(数字比较,需确保usage_num是纯数字)
    if [ $usage_num -gt $threshold ]; then
        # 终端报警(红色字体突出警告)
        echo -e "\033[31m 警告:磁盘分区 $mount 使用率已达 $usage ,超过阈值 $threshold% \033[0m"
        # 日志记录(包含时间戳,便于后续排查)
        echo "$(date +'%Y-%m-%d %H:%M:%S'):警告:$mount 使用率 $usage ,超过阈值 $threshold%" >> /var/log/disk_monitor.log
    fi
done

# 补充拓展:while read 其他常用场景(运维实战)
# 场景1:读取文件内容,逐行处理(如读取配置文件)
# cat /etc/nginx/nginx.conf | while read line; do
#     echo "配置行:$line"
# done

# 场景2:批量处理文件路径(结合find命令)
# find /var/log -name "*.log" | while read log_file; do
#     du -sh $log_file  # 查看每个日志文件大小
# done

4. 批量主机存活检测脚本(ping测试,输出存活状态)

bash 复制代码
#!/bin/bash
# 脚本功能:批量检测指定IP段的主机存活状态,输出结果到文件

# 定义IP段(可修改为自己的网段)
ip_prefix="192.168.1."
# 定义IP范围(1-20)
start=1
end=20
# 输出结果文件
result_file="/var/log/host_alive.log"

# 清空结果文件(可选)
> $result_file

echo "主机存活检测开始时间:$(date)" >> $result_file
echo "==============================" >> $result_file

for ((i=start; i<=end; i++)); do
    ip="${ip_prefix}${i}"
    # ping测试(2次,超时1秒)
    ping -c 2 -W 1 $ip > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo "$ip :存活" >> $result_file
        echo "$ip :存活"
    else
        echo "$ip :宕机" >> $result_file
        echo "$ip :宕机"
    fi
done

echo "检测完成时间:$(date)" >> $result_file

四、运维脚本实战技巧(避坑+优化)

1. 脚本避坑要点(重中之重)

  • 变量加双引号 :避免变量中包含空格、特殊字符导致报错,例如 [ -f "$log_path" ],而非 [ -f $log_path ]

  • 判断返回值 :用 $? 判断上一条命令是否执行成功(0=成功),避免脚本"静默失败"。

  • 避免使用rm -rf * :批量删除时,务必指定具体后缀或路径,防止误删(例如 rm -rf *.log,而非 rm -rf *)。

  • 添加日志记录:所有关键操作(重启服务、删除文件)必须写入日志,便于后续排查问题。

  • 脚本开头加参数校验:接收参数的脚本,先判断参数个数、参数合法性,避免因参数错误导致脚本执行异常。

  • 使用绝对路径 :脚本中所有命令、文件路径均使用绝对路径(例如 /usr/bin/ps/var/log/nginx),避免环境变量问题导致命令找不到。

2. 脚本优化技巧

  • 后台运行脚本 :监控类脚本需后台运行,添加 & 后缀,例如 ./monitor.sh &,避免终端关闭后脚本停止。

  • 加入开机自启 :将脚本路径添加到 /etc/rc.d/rc.local(CentOS)或 /etc/rc.local(Ubuntu),确保服务器重启后脚本自动运行(需给rc.local加执行权限)。

  • 简化命令 :使用awk、sed等工具简化命令,例如提取磁盘使用率:df -hl | awk '{print $5}',避免多步管道操作。

  • 添加错误处理 :使用 set -e(脚本遇到错误立即退出),或 trap 捕获信号,避免脚本继续执行导致更严重问题。

3. 常用辅助命令(脚本中高频使用)

bash 复制代码
awk     # 文本处理,提取字段(如提取IP、使用率)
sed     # 文本替换、删除(如修改配置文件)
grep    # 过滤关键字(如查找错误日志、进程)
find    # 查找文件(如查找过期日志、指定类型文件)
date    # 获取时间(如日志时间戳)
systemctl # 管理系统服务(启动、停止、重启)
ps      # 查看进程
df/du   # 查看磁盘空间
ping    # 主机存活检测
curl/wget # 接口、文件下载(如监控接口可用性)

五、常见问题排查(脚本执行报错)

  • 脚本无法执行 :未添加执行权限,执行 chmod +x 脚本名.sh;或首行解释器错误(如写错#!/bin/bash)。

  • 变量报错:等号两边有空格,或变量未定义就使用,检查变量定义语法。

  • 命令找不到:使用了相对路径,改为绝对路径(如将ps改为/usr/bin/ps)。

  • 循环报错:for/while循环语法错误,检查do、done是否配对,分号是否正确。

  • 权限不足 :脚本执行需要root权限,添加sudo(如 sudo ./脚本名.sh)。

六、进阶拓展(运维进阶必备)

  • 函数封装:将重复代码封装为函数(如日志记录、服务检查),简化脚本,提高复用性。

  • 邮件报警:在脚本中添加邮件发送功能(使用mailx命令),异常时自动发送报警邮件给运维人员。

  • 参数解析:使用getopts命令解析长参数、短参数,让脚本更灵活(如支持--help、--threshold等参数)。

  • 脚本调试 :使用 bash -x 脚本名.sh 调试脚本,查看每一步执行过程,快速定位错误。

  • 批量部署:结合expect脚本,实现无交互批量执行脚本(如批量部署服务、批量修改配置)。

1. Linux Shell函数详解

Shell函数是将一段可重复使用的代码块封装起来,起一个唯一名称,后续通过名称即可调用,核心作用是简化脚本、提高代码复用性、便于维护,尤其适合运维脚本中重复执行的操作(如日志记录、服务状态检查、错误处理等)。以下从语法、调用方式、实战案例、避坑要点全面解析。

1.1 函数基础语法

Shell函数无返回值类型,无需声明参数类型,语法简洁,优先推荐第一种标准写法,兼容性最好。

bash 复制代码
# 写法1:标准写法(推荐,兼容所有bash版本)
函数名() {
    # 函数体:要执行的命令、逻辑
    命令1
    命令2
    # 可选:return 退出状态码(0=成功,非0=失败,默认返回最后一条命令的状态码)
    return 0
}

# 写法2:带function关键字(兼容bash,不推荐sh环境)
function 函数名 {
    函数体
}

# 写法3:简洁写法(适合简单函数,一行完成)
函数名() { 命令1; 命令2; }

# 示例:最简单的函数(输出提示信息)
hello() {
    echo "欢迎使用Shell函数示例"
}

1.2 函数调用方式

函数定义后,直接通过「函数名」调用,可传递参数、接收返回值,核心注意:函数必须先定义,再调用(顺序不可颠倒)。

bash 复制代码
# 1. 基础调用(无参数、无返回值)
hello() {
    echo "欢迎使用Shell函数示例"
}
# 调用函数(直接写函数名)
hello

# 2. 函数传递参数(运维高频)
# 函数内通过$1、$2...$n接收参数,$#接收参数个数,$@接收所有参数
log_record() {
    # 参数1:日志级别(info/error/warn),参数2:日志内容
    local level=$1  # local关键字:定义局部变量,仅在函数内生效(推荐)
    local content=$2
    local time=$(date +'%Y-%m-%d %H:%M:%S')
    # 写入日志文件
    echo "[$time] [$level] $content" >> /var/log/script.log
}
# 调用函数(传递2个参数)
log_record "info" "脚本启动成功"
log_record "error" "nginx服务重启失败"

# 3. 函数接收返回值(3种常用方式)
# 方式1:return返回状态码(仅0-255,适合判断成功/失败)
check_service() {
    local service=$1
    # 检查服务是否运行
    if ps -ef | grep -v grep | grep -q $service; then
        return 0  # 成功返回0
    else
        return 1  # 失败返回1
    fi
}
# 调用并判断返回值($?获取上一条命令/函数的返回值)
check_service "nginx"
if [ $? -eq 0 ]; then
    echo "nginx服务正常"
else
    echo "nginx服务异常"
fi

# 方式2:echo输出返回值(适合返回字符串、数字,最常用)
get_disk_usage() {
    local mount=$1
    # 提取指定挂载点的使用率(去掉%)
    local usage=$(df -hl | grep $mount | awk '{print $5}' | sed 's/%//')
    echo $usage  # 输出结果,作为返回值
}
# 调用并接收返回值(用变量接收echo输出)
usage=$(get_disk_usage "/")
echo "根目录使用率:$usage%"

# 方式3:全局变量返回(不推荐,易污染全局变量)
global_var=""
set_var() {
    global_var="hello"  # 直接修改全局变量
}
set_var
echo $global_var  # 输出hello

1.3 运维实战函数案例

案例1:日志记录函数(所有脚本通用)
bash 复制代码
# 函数功能:统一记录日志,包含时间戳、日志级别、日志内容,支持自定义日志路径
log() {
    # 参数1:日志级别(info/error/warn/debug)
    # 参数2:日志内容
    # 参数3:日志路径(可选,默认/var/log/script.log)
    local level=$1
    local content=$2
    local log_path=${3:-/var/log/script.log}  # 默认值:无第三个参数时使用默认路径
    local time=$(date +'%Y-%m-%d %H:%M:%S')
    
    # 判断日志目录是否存在,不存在则创建
    local log_dir=$(dirname $log_path)
    if [ ! -d "$log_dir" ]; then
        mkdir -p $log_dir
    fi
    
    # 写入日志(不同级别可添加颜色,可选)
    case $level in
        error)
            echo -e "[$time] \033[31m[$level]\033[0m $content" >> $log_path
            ;;
        warn)
            echo -e "[$time] \033[33m[$level]\033[0m $content" >> $log_path
            ;;
        *)
            echo "[$time] [$level] $content" >> $log_path
            ;;
    esac
}

# 调用示例(融入磁盘监控脚本)
threshold=80
df -hl | grep -v tmpfs | grep -v squashfs | awk '{print $5,$6}' | while read usage mount; do
    usage_num=${usage%\%}
    if [ $usage_num -gt $threshold ]; then
        log "error" "磁盘分区$mount使用率达$usage,超过阈值$threshold%"
        echo -e "\033[31m 警告:磁盘分区 $mount 使用率已达 $usage ,超过阈值 $threshold% \033[0m"
    else
        log "info" "磁盘分区$mount使用率$usage,正常"
    fi
done
案例2:服务状态检查函数(适配所有systemctl管理的服务)
bash 复制代码
# 函数功能:检查服务状态,返回状态码(0=运行,1=未运行,2=服务不存在)
check_service_status() {
    local service=$1
    # 检查服务是否存在
    if ! systemctl list-unit-files | grep -q "$service.service"; then
#systemctl list-unit-files:列出系统中所有已注册的服务单元文件(包含所有已安装 / 配置的服务)
#grep -q "$service.service":静默匹配(-q 不输出匹配结果,仅返回状态码),判断服务对应的单元文件是否存在;
#!:取反,即 "如果没有匹配到该服务的单元文件"(说明服务不存在)
        log "error" "服务$service不存在"
        return 2
    fi
    # 检查服务是否运行
    if systemctl is-active --quiet $service; then
#systemctl is-active:用于检查系统服务的运行状态(active = 运行中,inactive = 未运行);
#--quiet:静默模式,不输出具体状态信息,仅返回状态码(0 = 运行中,非 0 = 未运行);
        log "info" "服务$service正在运行"
        return 0
    else
        log "warn" "服务$service未运行"
        return 1
    fi
}

# 调用示例(批量检查多个服务)
services=("nginx" "mysqld" "sshd")
for service in "${services[@]}"; do
    check_service_status $service
    case $? in
#$? 是 Linux Shell 的内置变量,代表「上一条命令 / 函数的返回值」
        0)
            continue  # 服务正常,跳过
            ;;
#continue 解析:跳过当前循环的剩余代码,直接进入下一次循环,即 "服务正常就不做任何操作,继续检查下一个服务"
        1)
            log "info" "正在重启服务$service"
            systemctl restart $service
            ;;
        2)
            log "error" "跳过服务$service,服务不存在"
            ;;
    esac
done
案例3:错误处理函数(统一处理脚本异常)
bash 复制代码
# 函数功能:脚本执行异常时,记录错误日志并退出脚本
error_handler() {
    local error_msg=$1
    local exit_code=${2:-1}  # 默认退出码1
    # 记录错误日志
    log "error" "脚本执行异常:$error_msg"
    # 输出终端提示
    echo -e "\033[31m 错误:$error_msg \033[0m"
    # 退出脚本
    exit $exit_code
}

# 调用示例(脚本开头校验参数)
if [ $# -ne 1 ]; then
    error_handler "参数错误,用法:$0 {start|stop|restart}"  # 调用错误处理函数
fi

# 调用示例(执行命令失败时触发)
systemctl restart nginx || error_handler "nginx服务重启失败"  # || 表示前一条命令失败时执行

${2:-1}

  • 这是 Shell 的参数默认值语法 ,核心逻辑:"如果函数接收了第二个参数($2),就用$2的值;如果没接收第二个参数($2为空),就用默认值1";

  • 拆解符号:

    • $2:代表函数接收的第二个参数error_handler函数中,第一个参数$1是错误提示信息,第二个参数$2就是自定义的退出码);
    • :-:固定语法,用于 "判断参数是否为空,为空则使用后面的默认值";
    • 1:默认退出码(自定义规则,运维中常用1表示 "默认执行异常",可根据需求修改为其他非 0 数字,比如用2表示 "参数错误"、3表示 "服务异常")。
  • $#

    • Shell 内置变量,核心作用:获取脚本接收的 "参数总个数"
    • 举例:执行 ./service.sh start(脚本名 + 1 个参数),则 $# = 1;执行 ./service.sh start nginx(2 个参数),则 $# = 2;直接执行 ./service.sh(无参数),则 $# = 0
  • -ne

    • 条件判断中的 "比较运算符",表示 "不等于"(全称 not equal),仅用于 数字比较(此处比较参数个数,属于数字)。
    • 补充常用数字运算符(运维必记):
      • -eq:等于(equal),如 $# -eq 1(参数个数等于 1)
      • -gt:大于(greater than),如 $# -gt 1(参数个数大于 1)
      • -lt:小于(less than),如 $# -lt 1(参数个数小于 1)
案例4:磁盘空间检查函数(复用性强)
bash 复制代码
# 函数功能:检查指定挂载点的磁盘使用率,超过阈值则报警
check_disk_usage() {
    local mount=$1
    local threshold=$2
    # 提取使用率(处理异常情况:挂载点不存在)
    local usage=$(df -hl | grep -w $mount | awk '{print $5}' | sed 's/%//')
    if [ -z "$usage" ]; then
        error_handler "挂载点$mount不存在"
    fi
    # 判断是否超过阈值
    if [ $usage -gt $threshold ]; then
        log "error" "挂载点$mount使用率$usage%,超过阈值$threshold%"
#调用前面日志函数
        echo -e "\033[31m 警告:挂载点$mount使用率$usage%,超过阈值$threshold% \033[0m"
    else
        log "info" "挂载点$mount使用率$usage%,正常"
    fi
}

# 调用示例(批量检查多个挂载点)
mounts=("/" "/data" "/var")
threshold=80
for mount in "${mounts[@]}"; do
    check_disk_usage $mount $threshold
done

1.4 函数避坑要点(运维必看)

  • 函数顺序:必须先定义函数,再调用函数,否则会报"command not found"错误(脚本从上到下执行)。

  • 局部变量 :函数内使用local定义变量,避免变量污染(局部变量仅在函数内生效,不影响全局变量)。

  • 参数传递:函数参数通过1、2...传递,与脚本参数区分开(脚本参数是脚本外部传入,函数参数是调用时传入)。

  • 返回值陷阱:return仅能返回0-255的状态码,无法返回字符串或大于255的数字;返回字符串/数字需用echo输出,再用变量接收。

  • 函数命名:避免与系统命令重名(如ls、cd、ps等),建议加前缀(如log_、check_),例如log_record而非log。

  • 递归调用:Shell函数支持递归(自己调用自己),但需设置终止条件,否则会陷入死循环,耗尽系统资源(运维脚本中极少使用)。

1.5 函数与脚本的结合技巧

  • 将常用函数(如日志、错误处理)封装到单独的文件(如func.sh),其他脚本通过source func.sh引入,实现函数复用(无需重复编写)。

  • 复杂脚本中,将不同功能拆分到不同函数(如日志函数、检查函数、执行函数),脚本主逻辑仅负责调用函数,提高可读性和可维护性。

  • 函数内可调用其他函数,实现功能嵌套(如错误处理函数调用日志函数,统一记录错误信息)。

2、实战脚本(批量备份+批量执行)

1. 简单批量备份脚本(生产常用)

功能:批量备份指定目录下的文件/文件夹,按日期命名备份包,自动清理7天前的旧备份(避免占用磁盘),适配大多数Linux系统。

bash 复制代码
#!/bin/bash
# 批量备份脚本:备份指定目录,自动清理旧备份(生产环境简化版,直接修改参数即可使用)
# 核心功能:1. 批量备份多个目录 2. 按日期命名备份包 3. 自动清理过期备份 4. 输出执行结果提示
# 适用场景:日常运维中,对网站目录、配置文件、日志等重要数据的定时/手动备份

# 1. 配置备份参数(可根据实际需求灵活修改,无需改动后续执行逻辑)
# 多个目录用空格分隔,示例:"/home /root",可根据自身运维需求调整
BACKUP_DIR="/data/www /etc/nginx /var/log"  # 需备份的目标目录集合
BACKUP_DEST="/backup"                      # 备份文件存放目录(建议选择磁盘空间充足的分区,避免备份失败)
RETENTION_DAYS=7                           # 备份保留天数,超过该天数的备份将自动删除,避免占用过多磁盘
DATE=$(date +%Y%m%d_%H%M%S)                # 日期格式(年-月-日_时-分-秒),用于备份包命名,防止不同时间的备份重复

# 2. 检查备份存放目录,不存在则自动创建(核心目的:防止因目录不存在导致备份任务中断)
if [ ! -d "$BACKUP_DEST" ]; then           # 条件判断:检测BACKUP_DEST指定的目录是否存在
    mkdir -p "$BACKUP_DEST"                # 递归创建目录(-p参数可自动创建上级目录,避免手动创建的繁琐)
    echo "创建备份目录:$BACKUP_DEST"      # 输出提示信息,便于用户确认目录创建状态
fi

# 3. 批量备份核心逻辑:循环遍历每个需备份的目录,逐个执行压缩备份操作
for dir in $BACKUP_DIR; do                 # 循环遍历:将BACKUP_DIR中的每个目录依次赋值给变量dir,逐个处理
    # 先判断当前遍历的目录是否存在,避免因目录不存在导致脚本报错、中断整个备份任务
    if [ -d "$dir" ]; then                 # 条件判断:确认当前目录存在后,再执行备份操作
        # 备份包命名规则:目录basename_日期.tar.gz(压缩格式,节省磁盘空间,同时便于区分不同备份)
        # basename "$dir":提取目录的基础名称(例:/data/www → www),让备份包名更简洁易识别
        BACKUP_NAME=$(basename "$dir")"_$DATE.tar.gz"
        # 执行压缩备份操作:tar命令是Linux常用备份工具,参数含义如下
        # -z:用gzip压缩备份包,节省磁盘空间;-c:创建新的备份包;-f:指定备份包的文件名及路径
        tar -zcf "$BACKUP_DEST/$BACKUP_NAME" "$dir"
        # 备份结果校验:判断上一条tar备份命令是否执行成功($? -eq 0 表示执行成功,非0则失败)
        if [ $? -eq 0 ]; then
            echo "✅ 备份成功:$dir → $BACKUP_DEST/$BACKUP_NAME"
        else
            echo "❌ 备份失败:$dir(常见原因:权限不足、目录被占用或磁盘空间不足)"
        fi
    else
        # 若目录不存在,输出提示并跳过该目录,不中断整个批量备份任务,保证其他目录正常备份
        echo "⚠️  跳过:目录 $dir 不存在,无需执行备份操作"
    fi
done

# 4. 自动清理过期备份:删除超过保留天数的备份文件,释放磁盘空间(避免长期备份占用过多存储)
# find命令用于查找符合条件的文件,各参数含义如下:
# -name "*.tar.gz":仅匹配后缀为.tar.gz的备份文件,避免误删其他文件
# -mtime +$RETENTION_DAYS:匹配修改时间超过RETENTION_DAYS天的文件(+表示"超过")
# -delete:直接删除匹配到的过期备份文件(无需手动确认,修改RETENTION_DAYS时需谨慎)
find "$BACKUP_DEST" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
# 修正原脚本笔误(RETENTION → RETENTION_DAYS),确保清理逻辑正确,避免报错
echo "🗑️  已清理 $RETENTION_DAYS 天前的旧备份,释放磁盘空间"

# 脚本执行完毕提示,便于用户快速确认批量备份任务的整体状态
echo "====== 批量备份任务执行完毕 ======"

使用说明:修改 BACKUP_DIR(要备份的目录)、BACKUP_DEST(备份存放路径)、RETENTION_DAYS(保留天数),添加执行权限后即可运行,可配合crontab设置定时备份(如每天凌晨2点执行)。

2. 简单批量执行脚本(多服务器/多任务)

  • 单服务器批量执行命令
bash 复制代码
#!/bin/bash
# 单服务器批量执行脚本:循环执行多个预设命令,适用于单台服务器的批量运维操作
# 核心功能:批量执行命令、输出每一步执行结果、快速排查失败原因,提升运维效率
# 适用场景:批量重启服务、批量修改文件权限、批量清理日志等重复性运维操作

# 定义需批量执行的命令(采用数组形式,每一行一个命令,可根据运维需求灵活添加、删除或修改)
# 数组的优势:命令管理更清晰,后续通过循环直接调用,无需重复编写执行逻辑
commands=(
    "systemctl restart nginx"          # 重启nginx服务(适用于web服务器,确保服务正常运行,保障业务可用)
    "chmod 755 /data/www/*"            # 批量修改文件权限(755权限:所有者可读可写可执行,其他用户可读可执行,兼顾安全与可用)
    "find /var/log -name "*.log" -size +100M -delete"  # 删除大于100M的日志文件,避免日志占用过多磁盘空间
    "df -h"                            # 查看磁盘使用情况,验证日志删除后的磁盘空间释放效果,确认操作有效性
)

# 批量执行核心逻辑:循环遍历commands数组,逐个执行预设命令,同步输出执行结果
for cmd in "${commands[@]}"; do          # "${commands[@]}":遍历数组中的每个命令,依次赋值给变量cmd
    echo "📌 正在执行命令:$cmd"        # 输出当前执行的命令,便于用户跟踪任务进度,快速定位执行环节
    # 直接执行变量cmd对应的命令,同步输出命令执行结果,便于查看详细信息
    $cmd
    # 执行结果校验:$? 是上一条命令的执行状态码(0表示执行成功,非0表示执行失败)
    if [ $? -eq 0 ]; then
        echo "✅ 命令执行成功"
    else
        echo "❌ 命令执行失败(常见原因:权限不足、命令不存在或参数配置错误)"
    fi
    echo "-------------------------"  # 分隔线,将不同命令的执行结果清晰区分,便于后续查看和排查问题
done

# 脚本执行完毕提示,告知用户所有预设命令已执行完成,便于确认批量操作整体状态
echo "====== 批量执行任务完毕 ======"
  • 多服务器批量执行命令(免密登录版)
bash 复制代码
#!/bin/bash
# 多服务器批量执行脚本:基于ssh免密登录,批量在多台服务器执行相同命令(简化版,适用于小型服务器集群)
# 核心功能:通过ssh免密登录,统一在多台服务器执行命令,减少重复操作,提升集群运维效率
# 适用场景:多台服务器批量检查状态、批量重启服务、批量更新配置等集群运维操作

# 1. 配置目标服务器信息(数组形式,格式:用户名@服务器IP,多个服务器用空格分隔)
# 注意事项:用户名需具备对应命令的执行权限(建议使用root用户,避免因权限不足导致命令执行失败)
servers=("root@192.168.1.10" "root@192.168.1.11" "root@192.168.1.12")
# 2. 定义需批量执行的命令(所有服务器统一执行,可根据需求修改)
# 多个命令用&&连接,按顺序执行;示例命令:检查sshd服务状态(确保ssh连接正常)+ 查看磁盘使用情况(检查服务器健康状态)
cmd="systemctl status sshd && df -h"

# 批量执行核心逻辑:循环登录每台目标服务器,执行预设命令,同步输出执行结果
for server in "${servers[@]}"; do         # 遍历servers数组,将每个服务器地址依次赋值给变量server
    echo "📌 正在连接服务器:$server"     # 输出当前连接的服务器信息,便于用户跟踪集群操作进度
    # ssh登录服务器并执行命令,输出详细执行结果,参数说明如下:
    # -o StrictHostKeyChecking=no:跳过ssh首次连接的密钥确认步骤,避免脚本中断(需提前配置免密登录)
    ssh -o StrictHostKeyChecking=no "$server" "$cmd"
    # 执行结果校验:判断上一条ssh命令是否执行成功,同步提示用户
    if [ $? -eq 0 ]; then
        echo "✅ 服务器 $server 命令执行成功"
    else
        echo "❌ 服务器 $server 命令执行失败(常见原因:免密登录未配置、服务器未开机、IP地址错误或端口不通)"
    fi
    echo "-------------------------"      # 分隔线,将不同服务器的执行结果清晰区分,便于排查单台服务器问题
done

# 脚本执行完毕提示,告知用户所有目标服务器的命令已执行完成,确认集群批量操作整体状态
echo "====== 多服务器批量执行完毕 ======"
相关推荐
乘云数字DATABUFF2 天前
5分钟部署开源APM Databuff:OpenTelemetry全链路追踪入门实战
运维·后端
荣--4 天前
一键部署不是为了省时间 —— 它是把"买来的 PaaS"变成"自己的平台"的拐点
运维·zabbix·工程化·一键部署·平台化·边界设计
江华森4 天前
动手实战学 Docker — 从零到集群编排完全指南
运维
Avan_菜菜4 天前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
SelectDB5 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
XIAOHEZIcode7 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220707 天前
如何搭建本地yum源(上)
运维
大树8810 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠10 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质10 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务