4. 流程控制(上):if-else判断、case多分支、test命令与[]的用法
脚本不是从头跑到尾的线性代码。你需要让它“思考”——根据不同的日志内容,走不同的处理路径。就像巡检时,遇到错误日志要告警,正常日志就归档。这套逻辑,就是流程控制。
我个人习惯把条件判断看成脚本的“大脑”。没有它,脚本就是个死板的打字员。今天我们先啃下最核心的三块:if-else、case,以及经常被搞混的test命令和[]。搞懂了它们,你的巡检脚本就能“活”起来。
4.1 if-else:脚本里的交通警察
if-else的作用很简单:检查一个条件,决定走哪条路。格式长这样:
if 条件测试; then
# 条件成立时执行的指令
else
# 条件不成立时执行的指令
fi
我刚开始写巡检脚本时,总觉得fi是打错了。后来被同事笑了一回,才记住它是if的反写——Shell的设计者挺调皮。记住这个,你永远不会漏掉fi。
举个实际场景:检查日志文件中是否出现了“ERROR”。
if grep -q "ERROR" /var/log/app.log; then
echo "检测到错误,请立即处理"
# 这里可以调用告警接口
else
echo "日志正常,无错误"
fi
关键点:grep -q不输出结果,只返回退出码。0代表找到了,非0代表没找到。if直接吃退出码,这是Shell的神奇之处。
你还可以用elif做多重判断。比如按错误级别分流:
if grep -q "CRITICAL" /var/log/app.log; then
echo "严重故障,立即关机"
elif grep -q "WARNING" /var/log/app.log; then
echo "警告,需关注"
else
echo "一切正常"
fi
我曾经写过一个巡检脚本,每天凌晨扫描Nginx错误日志。一开始只用了if-else,后来发现不同错误需要不同处理——磁盘写满要扩盘,权限错误要通知运维。加上elif之后,脚本才真正长成“能干活”的样子。
4.2 case多分支:比elif更清爽的选择
如果分支超过三个,elif会让代码像楼梯一样越缩越深。这时候我推荐用case。它的语法像switch,但更灵活:
case 变量 in
模式1)
指令1
;;
模式2|模式3)
指令2
;;
*)
默认指令
;;
esac
注意双分号;;是分支结束符。我见过新手把;;写成单个;,结果Shell把所有分支都执行了一遍——嗯,血的教训。
在日志巡检中,case特别好用来处理模糊匹配。比如找出低于阈值的各种异常:
read -p "输入日志级别: " level
case $level in
error|ERROR|Error)
echo "执行错误处理流程"
;;
warn*|WARN*)
echo "执行警告处理流程"
;;
info|INFO)
echo "仅记录,无操作"
;;
*)
echo "未知级别,忽略"
;;
esac
模式里可以用*通配符,warn*能匹配warning、warning_2等。你想想看,写4个elif要多少行?用case就舒服多了。
实战技巧:在审计日志巡检中,case经常配合日志分类代码使用。比如状态码200、404、500,直接写成200|201|204在一起,比if嵌套清爽十倍。
4.3 test命令与[]:条件测试的双胞胎
前面if和case里用的“条件测试”,本质都是调用test命令。test可以判断文件类型、数值大小、字符串是否为空。写起来有几种姿势:
test -f /tmp/log.txt # 判断文件是否存在且为普通文件
test "$a" -eq "$b" # 数值相等
[ -f /tmp/log.txt ] # 等价写法,注意空格!
[ "$a" -eq "$b" ] # 等价
[]其实就是test的简写,Shell会把它当作test命令来执行。核心原则:[]两边必须有空格。我见过最经典的错误:
# 错误的写法
[-f /tmp/log.txt] # [缺乏空格
[ "$a"= "$b" ] # 等号两边缺空格
曾经有个同事调试了一下午,就因为在[前面没加空格。Shell把[-f当成一个命令,报错说command not found。大家翻代码时都笑了——这个坑95%的新手都会踩一遍。
下面是test常用的比较操作符,我列个表方便你查阅:
| 分类 | 操作符 | 说明 | 示例 |
|---|---|---|---|
| 文件测试 | -f |
是否是普通文件 | [ -f /tmp/a.log ] |
-d |
是否是目录 | [ -d /var/log ] |
|
-s |
文件大小是否非0 | [ -s /tmp/error.log ] |
|
| 数值比较 | -eq |
等于 | [ "$count" -eq 0 ] |
-gt |
大于 | [ "$count" -gt 100 ] |
|
-lt |
小于 | [ "$count" -lt 50 ] |
|
| 字符串比较 | = |
字符串相等(注意只用单等号) | [ "$user" = "root" ] |
!= |
字符串不等 | [ "$user" != "nobody" ] |
|
-z |
字符串是否为空 | [ -z "$var" ] |
|
-n |
字符串是否非空 | [ -n "$var" ] |
数值比较只能用-eq、-gt这些字母,不能用>、<。后者是重定向符号,用了会出大事。我刚开始时就犯过这个错:
# 错误!会创建一个名为"100"的空文件
if [ "$count" > 100 ]; then ...
正确写法是[ "$count" -gt 100 ]。记住了。
避坑指南:变量最好加双引号,比如"$count"。如果变量未定义或为空,不加引号会导致语法错误。我曾经因为一次变量为空,导致if判断炸了,日志巡检中断了一个小时。
4.4 三种条件判断的配合套路
在实际的日志巡检脚本里,这三个工具往往搭配使用。比如先判断文件是否存在(test),如果存在就读取内容(if-else),根据内容模式走不同分支(case)。
我写过一个检查系统日志的脚本,核心逻辑大致如下:
# 检查日志文件是否存在
[ -f /var/log/syslog ] || exit 1
# 按关键字判断
if grep -q "OOM" /var/log/syslog; then
case "$(hostname)" in
web*)
echo "Web服务器OOM,重启nginx"
systemctl restart nginx
;;
db*)
echo "数据库OOM,增加swap"
fallocate -l 2G /swapfile
;;
*)
echo "未知服务器类型,记录告警"
;;
esac
elif grep -q "disk full" /var/log/syslog; then
echo "磁盘空间不足,清理日志"
find /var/log -type f -mtime +7 -delete
else
echo "系统日志正常"
fi
你看,[ -f ... ]做前置检查,if-elif-else处理不同错误类型,case根据主机名细化处理方式。三个工具拧在一起,脚本才真正有用。
这张图帮你理清今天讲的三样东西的关系。if-else和case是两路分支拓扑,而test/[]是它们共同的“判官”。记住这个结构,以后写条件判断时心里就有谱了。
小提示:如果条件里需要判断多个表达式(比如文件存在且非空),可以用-a(与)和-o(或)连接:[ -f /tmp/log.txt -a -s /tmp/log.txt ]。更现代化的写法是用[[ ]](双中括号),它支持&&和||,而且不用给变量加引号。不过这是进阶话题,我们在后面的章节再细聊。
好了,这一章的内容到这里。说白了,流程控制就是让脚本“变聪明”的基础。if-else让你能二选一,case让你能多选一,test和[]则是判断的按钮。把它们练熟了,日志巡检脚本就能按你设想的逻辑自动执行——不用人盯着,多省心。