无论什么编程语言都离不开条件判断。SHELL 也不例外。
例如,用户输入的密码不够长时提示”太短了”;用户输入了备份的目录,如果有目录继续备份,如果没有目录则创建目录;用户输入成绩,100-80 分评为优秀,60-79 分评为合格,59-0 分评为不合格。
本文将系统讲解 Shell 条件测试(数值、文件、字符串)、逻辑运算、if 流程控制、case 模式匹配,并通过实战案例带你掌握判断与流控的完整知识体系。
条件测试
基础格式
if [ 条件表达式 ]; then
# 条件为真时执行的命令
fi
# 方括号 [ ] 两边必须有空格,否则会报语法错误
#条件表达式中的变量建议用双引号包裹,防止空格或特殊字符导致解析错误
# 分号 ; 是命令分隔符,如果 then 写在新行,可以省略分号:
if [ 条件表达式 ]
then
# 命令
fi
数值比较
语法:
[ 整数1 操作符 整数2 ]
| 操作符 | 说明 | 示例 | 结果 |
|---|---|---|---|
-gt | 大于(greater than) | [ 20 -gt 10 ] | true |
-lt | 小于(less than) | [ 1 -lt 10 ] | true |
-eq | 等于(equal) | [ 1 -eq 1 ] | true |
-ne | 不等于(not equal) | [ 1 -ne 10 ] | true |
-ge | 大于等于(greater or equal) | [ 20 -ge 10 ] | true |
-le | 小于等于(less or equal) | [ 10 -le 10 ] | true |
获取变量长度的方法:
pass="hello"
echo ${#pass} # 输出 5
三种条件测试语法:
| 格式 | 语法 | 说明 |
|---|---|---|
| 格式 1 | test 条件表达式 | 最传统的写法 |
| 格式 2 | [ 条件表达式 ] | 最常用,注意方括号内两侧必须有空格 |
| 格式 3 | [[ 条件表达式 ]] | Bash 扩展语法,支持正则表达式等高级功能 |
密码长度测试案例
需求:猜测用户输入的密码,是否满足长度需求(至少 7 位)。
[root@localhost ~]# vim pass1.sh
#!/bin/bash
read -p "请输入您的密码:" ps
if [ ${#ps} -lt 7 ]; then
echo "您的密码太短!"
else
echo "您的密码真长!"
fi
测试:
[root@localhost ~]# bash pass1.sh
请输入您的密码:123
您的密码太短!
[root@localhost ~]# bash pass1.sh
请输入您的密码:1234567
您的密码真长!
文件测试
语法:
[ 操作符 文件或目录 ]
| 操作符(了解) | 说明 |
|---|---|
-f | 文件存在且为普通文件 |
-d | 目录存在 |
-e | 文件或目录存在 |
-r | 文件可读 |
-w | 文件可写 |
-x | 文件可执行 |
-s | 文件存在且大小大于 0 |
-b | 文件存在且是块文件 |
-c | 文件存在且是字符文件 |
-h 或 -L | 文件存在且是符号链接 |
-g | 文件存在并且设置了 SGID 位 |
-k | 文件存在并且设置了”粘滞”位 |
-p | 文件存在且是命名管道 |
-S | 文件存在且是 socket |
-t fd | fd 是与终端设备相关联的文件描述符 |
-O | 文件存在并且被当前进程的有效用户拥有 |
-G | 文件存在并且属于当前进程的有效用户的用户组 |
目录存在性测试案例
需求:请用户输入备份的路径,如果存在提示已存在可以备份,如果不存在提示目录不存在请创建。
[root@localhost ~]# vim path.sh
#!/bin/bash
read -p "请您输入备份的目录:" dir1
if [ -d "$dir1" ]; then
echo "$dir1 存在可以备份"
else
echo "$dir1 不存在无法备份"
fi
测试:
[root@localhost ~]# mkdir /abc
[root@localhost ~]# bash path.sh
请您输入备份的目录:/abc
/abc 存在可以备份
[root@localhost ~]# bash path.sh
请您输入备份的目录:/def
/def 不存在无法备份
字符串比较
语法:
[ "字符串" = "字符串" ]
| 操作符 | 说明 | 示例 |
|---|---|---|
= | 等于 | [ "$a" = "yes" ] |
!= | 不等于(注意叹号和等号间没有空格) | [ "$a" != "no" ] |
-z | 判断字符长度是否为 0 | [ -z "$str" ] |
-n | 判断字符长度是否不为 0 | [ -n "$str" ] |
字符串相等测试案例
需求:邀请用户确认,yes 升级装备,no 不升级装备。
[root@localhost ~]# vim yes.sh
#!/bin/bash
read -p "您确定要升级这件装备吗?" select
if [ "$select" = "yes" ]; then
echo "装备升级开始。。。"
else
echo "感谢您,欢迎下次光临。"
fi
测试:
[root@localhost ~]# bash yes.sh
您确定要升级这件装备吗?yes
装备升级开始。。。
[root@localhost ~]# bash yes.sh
您确定要升级这件装备吗?no
感谢您,欢迎下次光临。
双引号的重要性
双引号在字符串比较中非常重要,可以解决变量为空时的”一元表达式”错误。
[root@localhost ~]# BBB=""
[root@localhost ~]# echo ${#BBB}
0
# 使用双引号:正确
[root@localhost ~]# [ -z "$BBB" ] # 判断字符长度是否为 0
[root@localhost ~]# echo $?
0
[root@localhost ~]# [ -n "$BBB" ] # 判断字符长度是否不为 0
[root@localhost ~]# echo $?
1
# 不使用双引号:可能报错
[root@localhost ~]# [ -z $BBB ]
# 如果 BBB 为空,表达式变成 [ -z ],会报错
关键:在条件测试中使用变量时,务必加上双引号
"$变量",避免变量为空时产生语法错误。
逻辑运算(AND / OR)
简介
当条件测试比较复杂时,需要多个条件同时成立。就需要混合条件测试了。
| 符号 | 含义 | 说明 |
|---|---|---|
&& 或 -a | 逻辑 AND | 两个条件同时成立,为真 |
| ` | 或-o` |
多种表达方法:
# 使用 -a 和 -o(在 [] 中)
[root@localhost ~]# [ 1 -lt 2 -a 5 -gt 10 ] # AND
[root@localhost ~]# [ 1 -lt 2 -o 5 -gt 10 ] # OR
# 使用 && 和 ||(在 [[]] 中)
[root@localhost ~]# [[ 1 -lt 2 && 5 -gt 10 ]] # AND
[root@localhost ~]# [[ 1 -lt 2 || 5 -gt 10 ]] # OR
# 使用多个 [] 组合
[root@localhost ~]# [ 1 -lt 2 ] && [ 11 -gt 10 ] # AND
复杂密码验证案例
需求:猜测用户输入的密码是否满足如下条件:
- 长度大于等于 7 位
- 包含字母大写
- 包含字母小写
- 包含符号
@、_、!
[root@localhost ~]# vim pass3.sh
#!/bin/bash
read -p "请您输入新密码: " pass
if [ ${#pass} -ge 7 ] && [[ ${pass} =~ [a-z] ]] && [[ ${pass} =~ [A-Z] ]] && [[ ${pass} =~ [@_!] ]]; then
echo "您的密码真复杂!!!"
else
echo "您的密码太简单!!!"
fi
测试:
[root@localhost ~]# bash pass3.sh
请您输入新密码: 1234567
您的密码太简单!!!
[root@localhost ~]# bash pass3.sh
请您输入新密码: 1234567a
您的密码太简单!!!
[root@localhost ~]# bash pass3.sh
请您输入新密码: 1234567aZ
您的密码太简单!!!
[root@localhost ~]# bash pass3.sh
请您输入新密码: 123456aZ@
您的密码真复杂!!!
[root@localhost ~]# bash pass3.sh
请您输入新密码: 12345aZ!
您的密码真复杂!!!
注意:密码验证时要注意中文输入法的符号问题,中文符号和英文符号是不同的。
流程控制:if
单分支结构
语法:
if [ 条件测试 ]; then
符合该条件执行的语句
fi
示例:创建不存在的用户
#!/bin/bash
read -p "请输入用户名: " name
id $name &>/dev/null
if [ $? -ne 0 ]; then
useradd $name
fi
$? 变量说明:
$?包含了之前执行命令的退出状态。0 表示成功,非零表示失败。
双分支结构
语法:
if [ 条件表达式 ]; then
# 条件为真时执行的命令
else
# 条件为假时执行的命令
fi
示例:创建用户并设置密码
#!/bin/bash
read -p "请输入用户名: " name
if id $name &>/dev/null; then
echo "$name 已存在"
else
useradd $name
echo "123456" | passwd --stdin $name &>/dev/null
echo "$name 创建完成, 密码为 123456"
fi
注意:
if id $name &>/dev/null; then这种写法将条件测试直接放在了if后面,比使用$?更简洁。
多分支结构
语法:
if [ 条件表达式1 ]; then
# 条件1为真时执行
elif [ 条件表达式2 ]; then
# 条件1为假、条件2为真时执行
elif [ 条件表达式3 ]; then
# 条件1、2为假、条件3为真时执行
·
·
·
else
# 所有条件都为假时执行
fi
示例:根据系统时间判断时段
#!/bin/bash
hour=$(date +%H)
if [ $hour -ge 6 -a $hour -le 10 ]; then
echo "This is morning"
elif [ $hour -ge 11 -a $hour -le 13 ]; then
echo "This is noon"
elif [ $hour -ge 14 -a $hour -le 18 ]; then
echo "This is afternoon"
else
echo "This is night"
fi
嵌套结构
语法:
if [ 条件表达式1 ]; then
if [ 条件表达式2 ]; then
# 条件1和条件2都为真时执行
fi
else
fi
示例:创建用户并验证密码
#!/bin/bash
read -p "请输入用户名: " name
id $name &>/dev/null
if [ $? -eq 0 ]; then
echo "$name 存在"
else
useradd $name
echo "$name 创建成功"
read -p "请输入用户密码: " pass
if [ ${#pass} -ge 7 ]; then
echo "$pass" | passwd --stdin $name
echo "$name 用户密码是 $pass"
else
echo "密码不合格"
fi
fi
调试脚本
# 仅调试脚本中的语法错误
sh -n script.sh
# 以调试的方式执行,查询整个执行过程
sh -vx script.sh
| 选项 | 说明 |
|---|---|
-n | 读取脚本,检查语法错误,但不执行 |
-v | 显示读取的每一行 |
-x | 显示执行的每一行命令及其参数 |
注意事项
- 空格很重要:
[ ]表示条件测试,要注意在[后面和]前面都必须有空格 - then 和 fi 要分开:在 shell 中,then 和 fi 是分开的语句。如果要在同一行里面输入,则需要用分号将它们隔开:
if [ 条件 ]; then ; fi - 变量加引号:if 判断中对于变量的处理,需要加引号,以免一些不必要的错误。没有加双引号会在一些含空格等的字符串变量判断的时候产生错误。比如
[ -n "$var" ],如果 var 为空不加引号会出错 - 不支持浮点值:判断是不支持浮点值的,需要使用
bc等工具 >和<要转义:如果只单独使用>或者<号,系统会认为是输出或者输入重定向,虽然结果显示正确,但是其实是错误的,因此要对这些符号进行转义- 错误信息默认输出:运行 if 语句中的命令所产生的错误信息,仍然出现在脚本的输出结果中
-z和-n检查未定义变量:使用-z或者-n来检查长度的时候,没有定义的变量也为 0- 空变量可能造成灾难性影响:空变量和没有初始化的变量可能会对 shell 脚本测试产生灾难性的影响,因此在不确定变量的内容的时候,在测试号前使用
-n或者-z测试一下 $?变量:包含了之前执行命令的退出状态(最近完成的前台进程),可以用于检测退出状态
模式匹配:case
前言
Shell 编程中,
if和case都是用来做流程控制的。
if 的局限性:当需要匹配多个值时(如 y、Y、yes、YES 等),if 语句会变得非常冗长。
case 语法
case 变量 in
模式1)
命令序列1
;;
模式2)
命令序列2
;;
模式3)
命令序列3
;;
*)
无匹配后执行的命令序列
;;
esac
| 符号 | 说明 |
|---|---|
;; | 相当于 break,结束当前模式匹配 |
* | 通配符,相当于 else,匹配所有未指定的情况 |
| ` | ` |
案例 1:简单的模式匹配
需求:邀请用户输入待删除用户名,确认是否删除(支持多种输入)。
if 写法:
#!/bin/bash
read -p "请输入要删除的用户名 : " user
read -p "你确定删除该用户吗?[y/n]: " action
if [ "$action" = "y" -o "$action" = "Y" ]; then
userdel -r $user
echo "$user 已删除!"
else
echo "已退出,谢谢使用"
fi
case 写法:
#!/bin/bash
read -p "请输入要删除的用户名 : " user
read -p "确认删除吗?[yes/no] " action
case "$action" in
Y|y|YES|yes|Yes|YeS|YEs)
userdel -r $user
echo "$user 已删除!"
;;
*)
echo "已退出,谢谢使用"
;;
esac
对比:case 写法更加简洁、易读,特别是需要匹配多个值时。
案例 2:简单的 JumpServer(跳板机)
需求:通过 shell 编程,编写跳板程序。当我们需要访问服务器时,看一眼服务器列表名,按一下数字,就登录成功了。
先配置免密登录
#!/usr/bin/bash
# 定义目标主机 IP
web1=192.168.100.120
web2=192.168.100.123
mysql1=192.168.100.124
# 打印跳转菜单
cat <<EOF
1. WEB1
2. WEB2
3. MYSQL1
EOF
# 读取用户输入
read -p "请输入需要连接的服务器序号: " num
# 判断用户选择
case $num in
1)
ssh root@$web1
;;
2)
ssh root@$web2
;;
*)
echo "无效输入"
;;
esac
使用效果:
[root@localhost ~]# bash ssh.sh
1. WEB1
2. WEB2
3. MYSQL1
请输入需要连接的服务器序号: 2
Last login: Mon Jun 8 11:32:22 2026 from www.ceshi.com
[root@web2 ~]#
案例 3:系统管理工具箱
需求:Linux 提供了丰富的管理命令——用户管理、内存管理、磁盘管理、进程管理、日志管理、文件管理、软件管理、网络管理等等。通过 shell 编程,把它们编写到一个程序里。想用某些功能,只需要按回车,就能完成。
菜单选项:
| 按键 | 功能 | 命令 |
|---|---|---|
h | 显示命令帮助 | 显示菜单 |
f | 显示磁盘分区 | fdisk -l |
d | 显示磁盘挂载 | df -hT |
m | 查看内存使用 | free -m |
u | 查看系统负载 | uptime |
q | 退出程序 | exit |
实现脚本:
#!/usr/bin/bash
# 打印菜单
cat <<-EOF
h. 帮助
f. 磁盘分区信息
d. 文件系统挂载
m. 内存使用情况
u. 系统负载
q. 退出
EOF
# 读取用户输入,进行模式匹配
read -p "请输入选项 [h 查看帮助]: " action
case "$action" in
f)
fdisk -l
;;
d)
df -hT
;;
m)
free -m
;;
u)
uptime
;;
q)
exit
;;
"")
# 用户直接按回车,不做任何操作
;;
*)
echo "输入错误,请输入正确的选项"
;;
esac
使用效果:
h. 帮助
f. 磁盘分区信息
d. 文件系统挂载
m. 内存使用情况
u. 系统负载
q. 退出
请输入选项 [h 查看帮助]: m
total used free shared buff/cache available
Mem: 3771 118 3438 11 214 3407
Swap: 2047 0 2047
if vs case 对比
| 对比维度 | if | case |
|---|---|---|
| 适用场景 | 范围判断、数值比较、条件复杂 | 模式匹配、多值判断 |
| 语法复杂度 | 多条件时需要多个 elif | 多模式时更简洁 |
| 可读性 | 条件多时难以阅读 | 模式清晰,易于维护 |
| 匹配方式 | 精确的条件表达式 | 通配符模式匹配 |
选择建议:
- 需要判断范围、数值比较、多条件组合时 → 使用
if- 需要匹配多个具体值、菜单选择时 → 使用
case
常见问题
[ ] 和 [[ ]] 的区别
| 特性 | [ ] | [[ ]] |
|---|---|---|
| 兼容性 | POSIX 标准,所有 Shell 都支持 | Bash 扩展,仅 Bash 支持 |
| 正则表达式 | 不支持 | 支持 =~ 运算符 |
| 逻辑运算符 | 使用 -a、-o | 使用 &&、|| |
| 变量为空时 | 不加引号会报错 | 更宽容,不易出错 |
if 条件中的 then 可以写在同一行吗?
可以,但需要用分号隔开:
if [ 条件 ]; then
命令
fi
如何调试 if 脚本?
# 检查语法错误
sh -n script.sh
# 查看执行过程
sh -vx script.sh
$? 变量什么时候会被覆盖?
每执行一个命令,$? 都会被更新。所以如果要保留之前的退出状态,应该立即将其保存到变量中:
command
status=$?
echo $status
最佳实践
始终使用双引号包裹变量
if [ -n "$var" ]; then优先使用
[[ ]]替代[ ]if [[ "$str" =~ [a-z] ]]; then多值匹配使用
case替代冗长的if-elifcase "$action" in
y|Y|yes|YES) ... ;;使用
$(date +%H)替代反引号hour=$(date +%H) # 推荐
hour=`date +%H` # 不推荐使用
id user替代/etc/passwd检查用户if id $name &>/dev/null; then多分支 if 注意条件顺序
- 将最常见的条件放在前面,提高执行效率
总结
通过本章节的学习,你应该掌握了:
- 理解条件判断在 Shell 编程中的重要性
- 掌握三种条件测试语法(test、
[ ]、[[ ]]) - 掌握数值比较操作符(-gt、-lt、-eq、-ne、-ge、-le)
- 掌握文件测试操作符(-f、-d、-e、-r、-w、-x 等)
- 掌握字符串比较操作符(=、!=、-z、-n)
- 理解逻辑运算(AND/OR)的多种表达方式
- 掌握 if 单分支、双分支、多分支、嵌套结构
- 掌握 case 模式匹配的语法和用法
- 理解 if 和 case 的区别和适用场景
- 掌握脚本调试方法(sh -n、sh -vx)
- 能够编写密码验证、用户创建、跳板机、系统管理工具箱等实用脚本
学习建议:判断和流程控制是 Shell 编程的核心。建议在虚拟机上动手练习每一个示例,特别是条件测试中的空格问题和双引号的重要性。if 和 case 的区别要理解清楚,在实际开发中根据场景选择合适的流控方式。