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 "====== 多服务器批量执行完毕 ======"
相关推荐
2301_789015621 小时前
Linux:基础指令(二)
linux·运维·服务器·c语言·开发语言·c++·算法
wanhengidc1 小时前
云手机与正常手机的区别
大数据·运维·服务器·人工智能·智能手机
CableTech_SQH1 小时前
南通肿瘤医院智能化建设解析 以太网全光 + 低烟无卤 B1 方案百盛分析报告
大数据·运维·服务器·网络·信息与通信
Mortalbreeze1 小时前
深度理解进程 ---- 进程创建、进程终止、进程等待、进程替换 -> Shell的底层原理
linux·运维·服务器
ChampaignWolf1 小时前
SAP MCP服务器、SAP AI技能和Claude插件
运维·服务器·人工智能
Bert.Cai1 小时前
Linux expr命令详解
linux·运维·服务器
叶帆10 小时前
【YFIOs】Docker方式部署
运维·docker·容器
小猿姐11 小时前
Clickhouse Kubernetes Operator 实测:哪种方案更适合生产?
运维·数据库·kubernetes
彩色的黑'''12 小时前
[root@localhost ~]#,Linux系统的命令提示符为啥现在变成-bash-4.2#了,哪里设置的
linux·运维·bash