1. Shell脚本中的条件测试基础
在Shell编程中,条件测试是构建逻辑分支的核心机制。不同于其他编程语言使用布尔值直接判断,Shell通过退出状态码(exit status)来评估条件是否成立。每个命令执行后都会返回0-255之间的状态码,其中0表示成功(true),非0表示失败(false)。
测试命令主要有两种形式:
- test命令:
test expression - 方括号语法:
[ expression ](注意括号内侧必须有空格)
现代Shell还支持双括号语法[[ expression ]],它提供了更强大的功能且不需要严格空格限制。例如字符串比较时不需要引号也能正确处理含空格的情况:
bash复制if [[ $name == John Doe ]]; then # 无需引号
echo "Name matched"
fi
文件测试是Shell特有的强大功能,可以检查文件属性:
bash复制if [ -f "/path/to/file" ]; then # 检查常规文件是否存在
echo "File exists"
elif [ -d "/path/to/dir" ]; then # 检查目录
echo "Directory found"
fi
注意:在条件测试中,变量引用最好总是加双引号,避免变量为空或含空格时出现语法错误。例如
[ "$var" = "value" ]比[ $var = "value" ]更安全。
2. 条件测试的数值与字符串比较
2.1 数值比较操作符
Shell使用特定的操作符进行数值比较(与大多数编程语言不同):
-eq:等于(equal)-ne:不等于(not equal)-gt:大于(greater than)-lt:小于(less than)-ge:大于等于(greater or equal)-le:小于等于(less or equal)
典型示例:
bash复制if [ "$a" -gt "$b" ]; then
echo "$a is greater than $b"
fi
在双括号(( ))中可以使用更熟悉的数学比较符号(>, <, ==等):
bash复制if (( a > b )); then # 不需要$符号
echo "Math comparison works"
fi
2.2 字符串比较要点
字符串比较使用不同的操作符:
=或==:相等(在[]和[[]]中都可用)!=:不相等>和<:按字典顺序比较(仅在[[]]中有效)-z:字符串为空-n:字符串非空
关键区别示例:
bash复制str1="hello"
str2="world"
if [ "$str1" = "$str2" ]; then # 使用单个=号
echo "Strings are equal"
elif [[ $str1 < $str2 ]]; then # 双括号允许直接使用< >
echo "$str1 comes before $str2"
fi
实际经验:字符串比较时,
[[]]比[]更安全且功能更强。例如[[ $var == *.txt ]]可以直接做模式匹配,而[]需要借助外部命令如test。
3. 复合条件与逻辑运算符
3.1 逻辑组合方式
Shell提供三种逻辑运算符:
-a:AND(在[]中使用)-o:OR(在[]中使用)!:NOT
更现代的方式是使用:
&&:AND(在[[]]或命令间使用)||:OR(在[[]]或命令间使用)
对比示例:
bash复制# 传统方式
if [ "$age" -gt 18 -a "$age" -lt 60 ]; then
echo "Valid working age"
fi
# 现代方式
if [[ "$age" -gt 18 && "$age" -lt 60 ]]; then
echo "More readable syntax"
fi
3.2 条件组合的优先级控制
使用圆括号可以改变逻辑运算的优先级,但在[]中需要用反斜杠转义:
bash复制if [ \( "$count" -gt 0 -a "$count" -lt 100 \) -o "$flag" = "true" ]; then
echo "Complex condition"
fi
# 双括号中更清晰
if [[ ( "$count" > 0 && "$count" < 100 ) || "$flag" == "true" ]]; then
echo "Easier to read"
fi
调试技巧:在复杂条件语句中,可以先用
echo输出各个部分的测试结果,例如echo "[测试语句]结果是$?",通过查看返回值(0或1)来定位逻辑错误。
4. if-elif-else分支结构详解
4.1 基础语法结构
完整的if语句格式如下:
bash复制if 条件测试1; then
# 条件1为真时执行的命令
elif 条件测试2; then # 可选
# 条件2为真时执行的命令
else # 可选
# 所有条件都为假时执行的命令
fi # 结束标记
4.2 实际应用案例
考虑一个文件备份脚本的逻辑分支:
bash复制backup_dir="/var/backups"
today=$(date +%Y%m%d)
if [ ! -d "$backup_dir" ]; then
mkdir -p "$backup_dir"
echo "Created backup directory"
elif [ ! -w "$backup_dir" ]; then
echo "Error: Backup directory not writable" >&2
exit 1
fi
if tar -czf "$backup_dir/backup-$today.tar.gz" /home/user; then
echo "Backup succeeded"
if [ -f "$backup_dir/backup-$today.tar.gz" ]; then
ls -lh "$backup_dir/backup-$today.tar.gz"
fi
else
echo "Backup failed" >&2
exit 1
fi
4.3 嵌套if的处理技巧
当需要多层嵌套时,可以考虑:
- 使用函数封装内部逻辑
- 尽早返回减少嵌套层级
- 用
case语句替代深层if-elif
优化前:
bash复制if [ "$os" = "Linux" ]; then
if [ -f "/etc/redhat-release" ]; then
echo "RedHat based"
else
if [ -f "/etc/debian_version" ]; then
echo "Debian based"
fi
fi
fi
优化后:
bash复制check_distro() {
[ -f "/etc/redhat-release" ] && { echo "RedHat based"; return; }
[ -f "/etc/debian_version" ] && { echo "Debian based"; return; }
}
if [ "$os" = "Linux" ]; then
check_distro
fi
5. case语句:多分支的高级用法
5.1 基本语法结构
case语句适合处理多个确定值的匹配:
bash复制case "$变量" in
模式1)
命令序列1
;;
模式2)
命令序列2
;;
*) # 默认情况
默认命令
;;
esac
5.2 模式匹配的高级技巧
case支持通配符模式:
*:匹配任意字符串?:匹配单个字符[abc]:匹配a、b或c|:分隔多个模式
实际示例(处理用户输入):
bash复制read -p "Enter your choice (yes/no/quit): " choice
case "$choice" in
[yY]|[yY][eE][sS])
echo "You agreed"
;;
[nN]|[nN][oO])
echo "You refused"
;;
q|quit|exit)
echo "Exiting..."
exit 0
;;
*)
echo "Invalid choice" >&2
exit 1
;;
esac
5.3 与if语句的选择建议
适用case的场景:
- 有多个确定的字符串值需要匹配
- 需要利用通配符进行模式匹配
- 分支超过3个时代码更清晰
适用if的场景:
- 需要进行数值比较
- 条件测试比较复杂(如复合条件)
- 需要测试文件属性或命令返回值
6. 测试命令的返回值处理
6.1 直接使用命令返回值
Shell脚本中可以直接在if中执行命令并检查其返回值:
bash复制if grep -q "error" /var/log/syslog; then
echo "Found errors in syslog"
fi
6.2 组合命令的返回值处理
使用&&和||可以创建简洁的条件执行链:
bash复制# 只有前一个命令成功才会执行下一个
make && make install
# 只有前一个命令失败才会执行下一个
grep -q "error" logfile || echo "No errors found"
6.3 返回值检查的最佳实践
- 总是检查关键命令的返回值
- 在脚本开头设置
set -e可以在任何命令失败时立即退出脚本 - 使用
$?变量检查上一个命令的退出状态 - 为重要命令定义明确的退出码
示例:
bash复制#!/bin/bash
set -e # 出现错误立即退出
backup_files() {
tar -czf backup.tar.gz "$@" || {
echo "Backup failed" >&2
return 1
}
return 0
}
if ! backup_files /home/user /etc; then
exit 1
fi
7. 实战中的常见陷阱与调试技巧
7.1 典型错误案例
-
缺少空格:
bash复制if [$var="value"] # 错误:括号内侧需要空格 -
未引用的变量:
bash复制if [ -f $filename ]; then # 若filename为空会变成[ -f ] -
混淆字符串和数值比较:
bash复制if [ "$a" -eq "string" ]; then # 对非数值会报错
7.2 调试方法
- 使用
set -x开启调试模式,显示执行的每条命令 - 在关键位置添加
echo语句输出变量值和判断结果 - 使用
shellcheck工具静态检查脚本语法 - 临时添加
read暂停脚本执行以检查状态
7.3 性能优化建议
- 避免在循环中使用外部命令(如
grep、awk),尽量使用内置字符串操作 - 将不变的条件判断移出循环
- 对于复杂条件,考虑先用变量存储中间结果
- 在大量分支时,
case通常比if-elif效率更高
优化示例:
bash复制# 低效方式
while read line; do
if [ "$(echo "$line" | grep -c 'error')" -gt 0 ]; then
echo "$line"
fi
done < logfile
# 高效方式
while read line; do
if [[ "$line" == *error* ]]; then
echo "$line"
fi
done < logfile
8. 现代Shell的增强特性
8.1 双括号(( ))和[[ ]]扩展
Bash等现代Shell提供了更强大的测试语法:
(( )):算术运算和比较[[ ]]:增强的条件测试
优势包括:
- 不需要对变量引用加引号
- 支持
&&、||等标准逻辑运算符 - 在
[[ ]]中==支持通配符匹配 =~支持正则表达式匹配
正则匹配示例:
bash复制if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
echo "Valid email"
fi
8.2 数组支持
现代Shell支持数组操作的条件测试:
bash复制files=(*.txt)
if [ ${#files[@]} -eq 0 ]; then
echo "No txt files found"
else
echo "Found ${#files[@]} txt files"
fi
8.3 进程替换
结合条件测试和进程替换可以实现复杂逻辑:
bash复制if diff <(sort file1) <(sort file2); then
echo "Files have identical content"
fi
9. 跨平台兼容性考虑
9.1 不同Shell的差异
[是外部命令(通常是/bin/[),而[[是Shell关键字/bin/sh可能不支持[[ ]]和(( ))- 不同系统上的
test命令可能有细微差别
9.2 编写可移植脚本的建议
- 对于需要跨平台运行的脚本,使用
#!/bin/sh并保持POSIX兼容 - 避免使用Bash特有的功能(如数组、进程替换)
- 用
command -v代替which检查命令是否存在 - 测试时在多个Shell环境中运行(dash、bash、ksh等)
POSIX兼容示例:
bash复制#!/bin/sh
# 检查文件是否存在并可读
if [ -r "$file" ] && [ -f "$file" ]; then
echo "File is readable"
fi
# 字符串比较
if [ "$str1" = "$str2" ]; then
echo "Strings are equal"
fi
10. 综合应用案例:系统健康检查脚本
以下是一个结合多种条件测试技术的实际案例:
bash复制#!/bin/bash
# 阈值定义
CPU_WARN=80
MEM_WARN=90
DISK_WARN=90
# 检查CPU使用率
cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
if (( $(echo "$cpu_usage > $CPU_WARN" | bc -l) )); then
echo "[WARNING] High CPU usage: ${cpu_usage}%"
fi
# 检查内存使用
mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}')
if [ "$(echo "$mem_usage > $MEM_WARN" | bc)" -eq 1 ]; then
echo "[WARNING] High memory usage: ${mem_usage}%"
fi
# 检查磁盘空间
while read -r line; do
usage=$(echo "$line" | awk '{print $5}' | tr -d '%')
mount=$(echo "$line" | awk '{print $6}')
if [ "$usage" -ge "$DISK_WARN" ]; then
echo "[WARNING] Disk space low on $mount: ${usage}%"
fi
done < <(df -hP | grep -vE '^Filesystem|tmpfs|cdrom')
# 检查关键服务
services=("sshd" "nginx" "postgresql")
for service in "${services[@]}"; do
if ! systemctl is-active --quiet "$service"; then
echo "[CRITICAL] Service $service is not running!"
fi
done
# 根据问题数量设置退出状态
if [ $warning_count -gt 3 ] || [ $critical_count -gt 0 ]; then
exit 1
else
exit 0
fi
这个脚本展示了:
- 多种条件测试方法的组合使用
- 数值比较的不同实现方式
- 命令返回值的直接检查
- 复杂的多条件判断逻辑
- 可读性良好的警告分级输出