Home | 简体中文 | 繁体中文 | 杂文 | Github | 知乎专栏 | Facebook | Linkedin | Youtube | 打赏(Donations) | About
知乎专栏

2. test - 条件判断命令

test 命令用于条件判断,返回 0(真)或非 0(假)。在 shell 脚本中常用于 if、while 等控制结构。方括号 [ ] 是 test 命令的另一种写法。

判断目录

				
	test -d /path/to/directory || mkdir -p /path/to/directory
				
			

检查环境变量

			
		test -x $HAPROXY || exit 0
		test -f "$CONFIG" || exit 0
			
			

基本语法

test EXPRESSION
[ EXPRESSION ]

# 两种写法等价:
test -f /etc/passwd
[ -f /etc/passwd ]
			

注意:[EXPRESSION 以及 ] 之间必须有空格。

文件测试操作符

测试文件属性的常用操作符:

  • -e file - 文件存在

  • -f file - 文件存在且是普通文件

  • -d file - 文件存在且是目录

  • -r file - 文件存在且可读

  • -w file - 文件存在且可写

  • -x file - 文件存在且可执行

  • -s file - 文件存在且大小大于 0

  • -L file - 文件存在且是符号链接

  • -O file - 文件存在且属于当前用户

  • -G file - 文件存在且属于当前组

  • -N file - 文件存在且上次读取后被修改过

  • -c file - 文件存在且是字符特殊文件

  • -b file - 文件存在且是块特殊文件

  • -S file - 文件存在且是套接字

  • -p file - 文件存在且是命名管道

文件比较:

  • file1 -nt file2 - file1 比 file2 新(mtime)

  • file1 -ot file2 - file1 比 file2 旧

  • file1 -ef file2 - file1 和 file2 有相同的 device 和 inode

示例:

# 检查文件是否存在
test -f /etc/passwd && echo "文件存在"

# 检查目录是否存在
[ -d /var/log ] && echo "目录存在"

# 检查文件是否可执行
[ -x /usr/bin/munin-cron ] && /usr/bin/munin-cron

# 检查文件是否为空
if [ ! -s /tmp/log.txt ]; then
    echo "文件为空或不存在"
fi

# 比较两个文件的新旧
if [ file1.txt -nt file2.txt ]; then
    echo "file1.txt 比 file2.txt 新"
fi
			

字符串测试操作符

字符串比较操作符:

  • -z string - 字符串长度为 0(空字符串)

  • -n string - 字符串长度不为 0(非空字符串)

  • string1 = string2 - 字符串相等

  • string1 != string2 - 字符串不等

示例:

# 检查变量是否为空
name=""
if [ -z "$name" ]; then
    echo "变量为空"
fi

# 检查变量是否非空
name="neo"
[ -n "$name" ] && echo "变量非空: $name"

# 字符串相等比较
user="root"
if [ "$user" = "root" ]; then
    echo "是 root 用户"
fi

# 字符串不等比较
if [ "$user" != "guest" ]; then
    echo "不是 guest 用户"
fi

# 注意:变量应该用引号包裹,防止 word splitting
[ "$var" = "value" ]  # 正确
[ $var = "value" ]    # 危险:如果 $var 为空会报错
			

整数比较操作符

整数比较操作符:

  • n1 -eq n2 - n1 等于 n2

  • n1 -ne n2 - n1 不等于 n2

  • n1 -lt n2 - n1 小于 n2

  • n1 -le n2 - n1 小于等于 n2

  • n1 -gt n2 - n1 大于 n2

  • n1 -ge n2 - n1 大于等于 n2

示例:

# 检查数字是否相等
count=5
if [ "$count" -eq 5 ]; then
    echo "计数为 5"
fi

# 检查数字范围
age=25
if [ "$age" -ge 18 ] && [ "$age" -le 65 ]; then
    echo "年龄在 18-65 之间"
fi

# 检查进程数
procs=$(ps aux | wc -l)
if [ "$procs" -gt 100 ]; then
    echo "进程数过多: $procs"
fi

# 检查返回值
command
if [ $? -ne 0 ]; then
    echo "命令执行失败"
fi
			

记忆技巧:

  • eq = equal

  • ne = not equal

  • lt = less than

  • le = less than or equal

  • gt = greater than

  • ge = greater than or equal

逻辑操作符

组合多个条件:

  • expr1 -a expr2 - 逻辑与(expr1 和 expr2 都为真)

  • expr1 -o expr2 - 逻辑或(expr1 或 expr2 为真)

  • ! expr - 逻辑非

使用 shell 操作符(推荐):

  • expr1 && expr2 - 逻辑与

  • expr1 || expr2 - 逻辑或

  • ! expr - 逻辑非

示例:

# 逻辑与:文件存在且可执行
[ -f /usr/bin/munin-cron ] && [ -x /usr/bin/munin-cron ] && /usr/bin/munin-cron

# 使用 test 的 -a 操作符
test -f /etc/passwd -a -r /etc/passwd && echo "文件存在且可读"

# 逻辑或:目录不存在则创建
[ -d /tmp/test ] || mkdir /tmp/test

# 逻辑非:文件不存在
[ ! -f /tmp/lock ] && echo "锁文件不存在"

# 复杂条件组合
if [ -f "$file" ] && [ -r "$file" ] && [ -s "$file" ]; then
    echo "文件存在、可读且非空"
fi

# 嵌套条件
if [ "$user" = "root" ] || ([ "$user" = "admin" ] && [ -w "$file" ]); then
    echo "有权限"
fi
			

在 if 语句中使用

# 基本 if 结构
if [ -f /etc/passwd ]; then
    echo "文件存在"
fi

# if-else 结构
if [ -d /var/log ]; then
    echo "目录存在"
else
    echo "目录不存在"
fi

# if-elif-else 结构
if [ -f "$file" ]; then
    echo "是普通文件"
elif [ -d "$file" ]; then
    echo "是目录"
elif [ -L "$file" ]; then
    echo "是符号链接"
else
    echo "未知类型"
fi

# 多条件判断
if [ -f "$config" ] && [ -r "$config" ]; then
    source "$config"
elif [ -f "$default_config" ]; then
    source "$default_config"
else
    echo "没有配置文件"
    exit 1
fi
			

在 while/until 循环中使用

# while 循环:条件为真时执行
count=0
while [ $count -lt 5 ]; do
    echo "计数: $count"
    count=$((count + 1))
done

# until 循环:条件为假时执行
until [ -f /tmp/ready ]; do
    echo "等待文件创建..."
    sleep 2
done

# 读取用户输入
while [ "$answer" != "yes" ] && [ "$answer" != "no" ]; do
    read -p "请输入 yes 或 no: " answer
done
			

实际应用示例

# 检查命令是否存在
if [ -x /usr/bin/git ]; then
    echo "git 已安装"
else
    echo "git 未安装"
    exit 1
fi

# 检查用户权限
if [ "$(id -u)" -ne 0 ]; then
    echo "需要 root 权限运行"
    exit 1
fi

# 文件备份脚本
src="/data/important.db"
backup="/backup/$(date +%Y%m%d)_important.db"
if [ -f "$src" ] && [ -s "$src" ]; then
    cp "$src" "$backup"
    echo "备份成功: $backup"
else
    echo "源文件不存在或为空"
    exit 1
fi

# 检查磁盘空间
available=$(df / | tail -1 | awk '{print $4}')
threshold=1048576  # 1GB in KB
if [ "$available" -lt "$threshold" ]; then
    echo "警告:磁盘空间不足 1GB"
fi

# 等待服务启动
timeout=30
count=0
while [ $count -lt $timeout ]; do
    if [ -S /var/run/mysql.sock ]; then
        echo "MySQL 已启动"
        break
    fi
    sleep 1
    count=$((count + 1))
done

if [ $count -eq $timeout ]; then
    echo "MySQL 启动超时"
    exit 1
fi
			

[[ ]] 扩展测试(Bash 特有)

Bash 提供 [[ ]] 作为 [ ] 的增强版本:

  • 支持正则表达式匹配:=~

  • 支持 &&|| 在测试内部使用

  • 不需要对变量加引号(自动处理)

  • 支持模式匹配:== 配合通配符

# 正则表达式匹配
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "有效的邮箱地址"
fi

# 模式匹配
if [[ "$filename" == *.jpg ]]; then
    echo "是 JPEG 图片"
fi

# 内部使用逻辑操作符
if [[ -f "$file" && -r "$file" ]]; then
    echo "文件存在且可读"
fi

# 字符串比较(不需要引号)
if [[ $user == root ]]; then
    echo "是 root 用户"
fi

# 字典序比较
if [[ "apple" < "banana" ]]; then
    echo "apple 在 banana 之前"
fi
			

注意:[[ ]] 是 Bash 扩展,不兼容 POSIX sh。如果需要可移植性,使用 [ ]

常见陷阱

# 错误:方括号内没有空格
if [-f /etc/passwd]; then  # 错误!
if [ -f /etc/passwd ]; then  # 正确

# 错误:整数比较使用字符串操作符
if [ "$count" = "5" ]; then  # 字符串比较
if [ "$count" -eq 5 ]; then  # 整数比较(正确)

# 危险:未加引号的变量
if [ $file = "test.txt" ]; then  # 如果 $file 为空会报错
if [ "$file" = "test.txt" ]; then  # 安全

# 错误:使用 = 比较数字
if [ $age = 18 ]; then  # 字符串比较
if [ $age -eq 18 ]; then  # 整数比较(正确)

# 错误:在 [ ] 中使用 && 和 ||
if [ -f file1 && -f file2 ]; then  # 错误!
if [ -f file1 ] && [ -f file2 ]; then  # 正确
			

返回值检查

test 命令的返回值($?):

# 0 表示真,非 0 表示假
test -f /etc/passwd
echo $?  # 输出: 0(文件存在)

test -f /nonexistent
echo $?  # 输出: 1(文件不存在)

# 在脚本中检查返回值
command
if [ $? -eq 0 ]; then
    echo "命令成功"
else
    echo "命令失败,退出码: $?"
fi

# 简写形式
command && echo "成功" || echo "失败"