Shell test命令

test是Shell内置命令, 用来检测某个条件是否成立. test通常和if语句一起使用, 并且大部分if语句都依赖test.

test命令有很多选项, 可以进行数值, 字符串文件三个方面的检测.

test命令的用法为:

test expression

test判断expression成立时, 退出状态为0, 否则为非0值.

test命令也可以简写为[], 它的用法为:

[ expression ]

注意[]expression之间的空格, 这两个空格是必须的, 否则会导致语法错误. []的写法更加简洁, 比test使用频率高.

与文件检测相关的test选项

  • 文件类型判断
选项 作用
-b filename 判断文件是否存在, 并且是否为块设备文件.
-c filename 判断文件是否存在, 并且是否为字符设备文件.
-d filename 判断文件是否存在, 并且是否为目录文件.
-e filename 判断文件是否存在.
-f filename 判断文件是否存在, 并且是否为普通文件.
-L filename 判断文件是否存在, 并且是否为符号链接文件.
-p filename 判断文件是否存在, 并且是否为管道文件.
-s filename 判断文件是否存在, 并且是否为非空.
-S filename 判断文件是否存在, 并且是否为套接字文件.
  • 文件权限判断
选项 作用
-r filename 判断文件是否存在, 并且是否有读权限.
-w filename 判断文件是否存在, 并且是否有写权限.
-x filename 判断文件是否存在, 并且是否有执行权限.
-u filename 判断文件是否存在, 并且是否有SUID权限.
-g filename 判断文件是否存在, 并且是否有SGID权限.
-k filename 判断文件是否存在, 并且是否有SBIT权限.
  • 文件比较
选项 作用
filename1 -nt filename2 判断filename1的修改时间是否比filename2的新.
filename1 -ot filename2 判断filename1的修改时间是否比filename2的旧.
filename1 -ef filename2

判断filename1是否和filename2的inode号一致,

可以理解为两个文件是否为同一个文件,

这个判断用于判断硬链接是很好的方法.

Example:

#!/usr/bin/env bash

read filename
read url

if [ -w $filename -a -n $url ]
then
    echo $url > $filename
    echo "写入成功"
else
    echo "写入失败"
fi

与数值比较相关的test选项

选项 作用
num1 -eq num2 equal, 判断num1是否等于num2.
num1 -ne num2 not equal, 判断num1是否不等于num2.
num1 -gt num2 greather than, 判断num1是否大于num2.
num1 -ge num2 greather equal, 判断num1是否大于等于num2.
num1 -lt num2 less than, 判断num1是否小于num2.
num1 -le num2 less equal, 判断num1是否小于等于num2.

注意, test只能用来比较整数, 小数比较还得依赖bc命令.

Example:

#!/usr/bin/env bash

read a b

if [ $a -eq $b ]
then
    echo "两个数相等"
else
    echo "两个数不相等"
fi

与字符串判断相关的test选项

选项 作用
-z str 判断字符串str是否为空.
-n str 判断字符串str是否为非空.
str1 = str2 判断str1和str2是否相等.
str1 == str2 判断str1和str2是否相等.
str1 != str2 判断str1和str2是否不等.
str1 \> str2

判断str1是否大于str2.

\>>的转义字符, 防止被误认为成重定向运算符.

str1 /< str2

判断str1是否小于str2.

/<<的转义字符, 防止被误认为成重定向运算符.

在C/C++, Python等编程语言中, ==/>/<都被用来比较数字, 而在Shell的test命令中, 它们不能比较数字, 只能用来比较字符串.

与逻辑运算相关的test选项

选项 说明
expression1 -a expression2

逻辑与, 表达式expression1expression2都成立,

最终的结果才是成立的.

expression1 -o expression2

逻辑或, 表达式expression1expression2有一个成立,

最终的结果就成立.

!expression 逻辑非, 对expression进行取反.

Example:

#/usr/bin/env bash

read str1
read str2

# 检测字符串是否为空
if [ -z "$str1" -o -z "$str2" ]
then
    echo "字符串不能为空"
    exit 0
fi

# 比较字符串
if [ $str1 == $str2 ]
then
    echo "字符串相等"
else
    echo "字符串不相等"
fi

在test中使用变量建议用双引号包围起来

test[]都是命令, 一个命令本质上对应一个程序或者一个函数. 即使是一个程序, 它也有入口函数, 例如C语言程序的入口函数是main(), 运行C语言程序就从main()函数开始, 所以也可以将一个程序等效为一个函数, 这样我们就不用区分函数和程序了, 直接将一个命令和一个函数对应起来即可.

使用一个命令时, 命令后面附带的选项和参数最终都会作为实参传递给函数.

假设test命令对应的函数是func(), 使用test -z $str命令时, 会先将变量$str替换成字符串:

  • 如果$str是一个正常的字符串, 比如abc123, 那么替换后的效果就是test -z abc123, 调用func函数的形式就是func("-z abc123"). test命令后面附带的所有选项和参数会被看成一个整体, 并作为实参传递进函数;
  • 如果$str是一个空字符串, 那么替换后的效果就是test -z, 调用func()函数的形式就是func("-z"), 这就比较奇怪了, 因为-z选项没有和参数成对出现, func()在分析时就会出错.

如果我们给变量$str加上双引号, 当$str是空字符串时, test -z "$str"就会被替换为test -z "",调用func()函数的形式就是func("-z \"\""), 很显然, -z选项后面跟的是一个空字符串, 这样func()在分析时就不会出错了.

所以, 当在test命令中使用变量时, 强烈建议将变量用双引号包围起来, 这样能避免变量为空值时导致的很多奇葩问题.

总结

test命令比较奇葩, >/</==只能用来比较字符串, 不能用来比较数字, 比较数字需要使用-eq, -gt等选项; 不管是比较字符串还是数字, test都不支持>=<=.

对于整型数字的比较, 建议使用(()), (())支持各种运算, 写法也符合数学规则, 用起来更加方便.

几乎完全兼容test, 并且比test更加强大, 比test更加灵活的是: [[]], [[]]不是命令, 而是Shell关键字.