一、为什么需要逻辑控制?
第一篇我们编写的脚本,都是“顺序执行”——从第一行到最后一行,依次执行命令,没有任何判断和分支。如未阅读可翻看老王linux-进阶-shell编程(一)语法与运行规范
但是在实际工作中,我们经常需要根据不同的条件执行不同的操作,或者重复执行某一系列命令。
比如:
•判断某个目录是否存在,不存在则创建,存在则提示;
•判断服务是否运行,未运行则启动,运行则输出状态;
•批量处理100个文件,重复执行相同的操作;
•将重复使用的代码(如输出系统信息)封装起来,避免重复编写。
这些场景都需要用到Shell脚本的逻辑控制语法——if判断、循环、函数,这也是Shell脚本从“简单命令堆砌”到“真正自动化工具”的核心跨越。
二、条件判断 if
if判断是Shell脚本中最常用的逻辑语法,用于“根据条件是否成立,执行不同的命令”。核心格式分为3种:单分支、双分支、多分支。
2.1 格式
bash# 1. 单分支(条件成立则执行,不成立则不执行)if [ 条件判断 ]; then条件成立时执行的命令fi# 2. 双分支(条件成立执行一组命令,不成立执行另一组)if [ 条件判断 ]; then条件成立时执行的命令else条件不成立时执行的命令fi# 3. 多分支(多个条件,依次判断,满足一个则执行对应命令)if [ 条件1 ]; then条件1成立时执行的命令elif [ 条件2 ]; then条件2成立时执行的命令else所有条件都不成立时执行的命令fi
注意点:
•if 后面的[ ]两边必须有空格(如[ -d /root ],不能写成[ -d /root]);
•then 可以和if写在同一行(加空格分隔),也可以换行(推荐换行,可读性更强);
•if判断的结束必须用fi(if的反向拼写),不能遗漏。
2.2 场景1:判断文件/目录是否存在
工作中经常需要判断文件或目录是否存在(如备份目录、配置文件),再执行后续操作,这是if判断最常用的场景。
常用判断符:
•-d 路径:判断路径是否为目录(directory);
•-f 路径:判断路径是否为文件(file);
•-e 路径:判断路径是否存在(无论文件还是目录);
•! -d 路径:判断路径不是目录(!表示否定);
•! -f 路径:判断路径不是文件。
判断备份目录是否存在,不存在则创建,存在则提示
bash#!/bin/bash# 脚本功能:判断备份目录是否存在,不存在则创建# 备份目录路径(定义变量,方便后续修改)BACKUP_DIR="/data/backup/log"# 判断目录是否存在if [ ! -d $BACKUP_DIR ]; thenecho "备份目录 $BACKUP_DIR 不存在,正在创建..."# mkdir -p:递归创建目录(即使上级目录不存在,也能创建)mkdir -p $BACKUP_DIRecho "目录创建成功!"elseecho "备份目录 $BACKUP_DIR 已存在,无需创建。"fi
2.3 场景2:数字判断(比较大小)
用于比较两个数字的大小(如磁盘使用率、内存占用、计数等),常用在监控脚本中。
常用数字判断符:
•-gt:greater than,大于;
•-lt:less than,小于;
•-eq:equal,等于;
•-ne:not equal,不等于;
•-ge:greater or equal,大于等于;
•-le:less or equal,小于等于。
判断磁盘使用率,超过80%则提示告警,否则提示正常
bash#!/bin/bash# 脚本功能:判断磁盘使用率,超过80%提示告警# 获取根目录(/)的磁盘使用率(截取数字部分)# df -h:查看磁盘使用情况;grep /dev/vda1:筛选根目录所在分区;awk '{print $5}':获取第5列(使用率,如85%);sed 's/%//g':去掉%符号DISK_USE=$(df -h | grep /dev/vda1 | awk '{print $5}' | sed 's/%//g')# 判断使用率是否超过80%if [ $DISK_USE -gt 80 ]; thenecho "告警:根目录磁盘使用率过高,当前使用率:$DISK_USE%"echo "建议清理过期文件,释放磁盘空间!"elif [ $DISK_USE -ge 70 ] && [ $DISK_USE -le 80 ]; thenecho "警告:根目录磁盘使用率偏高,当前使用率:$DISK_USE%"echo "请关注磁盘使用情况!"elseecho "正常:根目录磁盘使用率:$DISK_USE%"fi
补充:&& 表示“并且”,两个条件都成立才执行;||表示“或者”,只要有一个条件成立就执行。
2.4 场景3:字符串判断
用于比较两个字符串是否相等、是否为空,常用在判断用户输入、配置参数等场景。
常用字符串判断符:
•$STR1 = $STR2:两个字符串相等(=两边有空格);
•$STR1 != $STR2:两个字符串不相等;
•-z $STR:字符串为空(zero);
•-n $STR:字符串不为空(not zero)。
判断用户输入的指令是否正确,正确则执行,错误则提示
bash#!/bin/bash# 脚本功能:判断用户输入的指令,执行对应操作echo "请输入指令(start/stop/restart):"# read:读取用户输入,存储到变量CMD中read CMD# 判断用户输入的字符串if [ $CMD = "start" ]; thenecho "正在启动服务..."# 这里可添加启动服务的命令,如systemctl start nginxelif [ $CMD = "stop" ]; thenecho "正在停止服务..."# 这里可添加停止服务的命令,如systemctl stop nginxelif [ $CMD = "restart" ]; thenecho "正在重启服务..."# 这里可添加重启服务的命令,如systemctl restart nginxelseecho "输入错误!请输入正确指令(start/stop/restart)"fi
三、循环(for、while)
循环用于“重复执行某一系列命令”,适合批量处理文件、批量执行命令、计数等场景。Shell脚本中最常用的两种循环:for循环和while循环,各有适用场景。
3.1 for循环(适合固定次数、固定列表的循环)
for循环的核心是“遍历一个列表”,列表中的每一个元素,都会执行一次循环体内的命令,适合批量处理已知的列表(如文件列表、数字列表)。
3.1.1 格式
bash# 格式1:遍历固定列表for 变量名 in 列表元素1 列表元素2 列表元素3do循环体内执行的命令(可调用变量)done# 格式2:遍历数字范围(常用)for 变量名 in {起始数字..结束数字}do循环体内执行的命令done# 格式3:遍历文件列表(常用)for 变量名 in $(ls 文件名匹配规则)do循环体内执行的命令done
3.1.2 案例1:批量创建10个文件
bash#!/bin/bash# 脚本功能:批量创建10个日志文件,命名格式:log_1.txt ~ log_10.txtecho "开始批量创建文件..."# 遍历数字1到10for i in {1..10}do# 创建文件,文件名包含循环变量itouch log_$i.txtecho "创建文件:log_$i.txt"doneecho "批量创建完成,共创建10个文件!"
3.1.3 案例2:批量修改文件名
将当前目录下所有.txt后缀的文件,批量修改为.log后缀。
bash#!/bin/bash# 脚本功能:批量修改文件名,.txt -> .logecho "开始批量修改文件名..."# 遍历当前目录下所有.txt文件for file in $(ls *.txt)do# ${file%.txt}:截取文件名,去掉.txt后缀(字符串操作)# mv:重命名文件mv $file ${file%.txt}.logecho "修改完成:$file -> ${file%.txt}.log"doneecho "批量修改完成!"
3.2 while循环(不确定次数、条件循环)
while循环的核心是“根据条件是否成立,重复执行循环体”,只要条件成立,就会一直执行,直到条件不成立时退出循环,适合不确定循环次数的场景(如计数、等待服务启动)。
3.2.1 格式
bashwhile [ 条件判断 ]; do循环体内执行的命令# (可选)修改条件变量,避免死循环变量更新命令done
注意:while循环一定要有“变量更新”步骤,否则会陷入死循环(一直执行,无法退出)。
3.2.2 案例1:计数(从1循环到5)
bash#!/bin/bash# 脚本功能:计数循环,从1循环到5,输出循环次数i=1 # 定义计数变量,初始值为1# 条件:i小于等于5时,继续循环while [ $i -le 5 ]doecho "循环第 $i 次"i=$((i+1)) # 变量更新:i自增1(避免死循环)doneecho "循环结束!"
3.2.3 案例2:等待服务启动
等待Nginx服务启动,每3秒检查一次,直到服务启动成功,输出启动成功信息
bash#!/bin/bash# 脚本功能:等待Nginx服务启动,直到启动成功echo "正在等待Nginx服务启动..."# 条件:检查Nginx进程是否存在,不存在则继续循环while [ -z "$(ps -ef | grep nginx | grep -v grep)" ]doecho "Nginx服务尚未启动,3秒后再次检查..."sleep 3 # 暂停3秒doneecho "Nginx服务启动成功!"
四、函数(封装重复代码)
在编写复杂脚本时,很多命令会重复使用(如输出系统信息、检查服务状态),如果每次都重复编写,会导致脚本冗长、难以维护。函数就是将这些重复的代码“封装”起来,需要时直接调用,提升脚本的可读性和可维护性。
4.1 格式
bash# 定义函数(两种格式,推荐第一种)函数名() {函数体内的命令(可包含变量、判断、循环)# (可选)返回值:用return,返回值范围0-255(0表示成功)return 0}# 调用函数(直接写函数名,无需加括号)函数名
4.2 案例1:封装系统信息输出函数
将输出系统信息的代码封装成函数,可多次调用,避免重复编写。
bash#!/bin/bash# 脚本功能:封装系统信息输出函数,多次调用# 定义函数:输出系统基础信息system_info() {echo "======================================"echo " 系统基础信息 "echo "======================================"echo "当前用户:$USER"echo "当前时间:$(date +%Y-%m-%d %H:%M:%S)"echo "系统内核:$(uname -r)"echo "当前目录:$PWD"echo "======================================"}# 第一次调用函数system_info# 执行其他命令echo "正在执行其他操作..."sleep 2# 第二次调用函数(无需重复编写代码)system_info
4.3 实例2:封装服务检查函数
封装一个函数,用于检查指定服务的运行状态,传入服务名作为参数,返回服务状态。
bash#!/bin/bash# 脚本功能:封装服务检查函数,可检查任意服务状态# 定义函数:检查服务状态,参数为服务名check_service() {# $1:函数的第一个参数(调用函数时传入的服务名)SERVICE_NAME=$1# 检查服务进程是否存在PID=$(ps -ef | grep $SERVICE_NAME | grep -v grep)if [ -n "$PID" ]; thenecho "$SERVICE_NAME 服务运行正常,PID:$(echo $PID | awk '{print $2}')"return 0 # 返回0,表示成功elseecho "$SERVICE_NAME 服务未运行!"return 1 # 返回1,表示失败fi}# 调用函数:检查Nginx服务check_service nginx# 调用函数:检查Docker服务check_service docker# 调用函数:检查Java服务check_service java
补充:函数的参数可以通过$1(第一个参数)、$2(第二个参数)、$*(所有参数)获取,非常灵活。
五、编写带逻辑的自动化脚本
结合本篇所学的if判断、循环、函数,编写一个综合脚本,实现“检查备份目录→创建目录→备份日志→检查备份是否成功”的完整流程,可直接用于生产环境。
bash#!/bin/bash# 脚本名称:log_backup.sh# 脚本功能:备份系统日志,包含目录检查、备份、校验# 定义变量(规范:所有变量大写)BACKUP_DIR="/data/backup/log" # 备份目录LOG_DIR="/var/log" # 日志目录DATE=$(date +%Y%m%d) # 当前日期(用于备份文件名)BACKUP_FILE="$BACKUP_DIR/log_backup_$DATE.tar.gz" # 备份文件名# 1. 定义函数:检查备份目录check_dir() {if [ ! -d $BACKUP_DIR ]; thenecho "备份目录 $BACKUP_DIR 不存在,正在创建..."mkdir -p $BACKUP_DIRif [ $? -eq 0 ]; thenecho "目录创建成功!"elseecho "目录创建失败,脚本退出!"exit 1 # 退出脚本,状态码1表示失败fielseecho "备份目录 $BACKUP_DIR 已存在,无需创建。"fi}# 2. 定义函数:备份日志(压缩打包)backup_log() {echo "开始备份日志,备份文件:$BACKUP_FILE"# tar -zcvf:压缩打包,-z用gzip压缩,-c创建打包文件,-v显示过程,-f指定文件名tar -zcvf $BACKUP_FILE $LOG_DIR/*.log# 判断备份是否成功($?为0表示成功)if [ $? -eq 0 ]; thenecho "日志备份成功!"elseecho "日志备份失败,脚本退出!"exit 1fi}# 3. 定义函数:检查备份文件check_backup() {if [ -f $BACKUP_FILE ]; thenecho "备份文件校验成功,文件大小:$(du -sh $BACKUP_FILE)"elseecho "备份文件不存在,备份失败!"exit 1fi}# 4. 调用函数,执行完整流程check_dirbackup_logcheck_backupecho "======================================"echo " 日志备份流程完成! "echo "======================================"