结构化命令 之 test命令(长文慎入)

本篇内容均摘自《Linux命令行与shell脚本编程大全》,个人认为需要重点学习的章节。【免费】Linux命令行与Shell脚本编程大全 第3版 PDF全本 21MB 百度网盘下载 - 今夕是何夕 - 博客园
到目前为止,在if语句中看到的都是普通shell命令。你可能想问, if-then语句是否能测试命令退出状态码之外的条件。答案是不能。但在bash shell中有个好用的工具可以帮你通过if-then语句测试其他条件。test命令提供了在if-then语句中测试不同条件的途径。如果test命令中列出的条件成立,test命令就会退出并返回退出状态码0。这样if-then语句就与其他编程语言中的if-then语句以类似的方式工作了。如果条件不成立, test命令就会退出并返回非零的退出状态码,这使得if-then语句不会再被执行。test命令的格式非常简单:test condition。
condition是test命令要测试的一系列参数和值。当用在if-then语句中时, test命令看起来是这样的。
if test condition
then
commands
fi
如果不写test命令的condition部分,它会以非零的退出状态码退出, 并执行else语句块。

$ cat test6.sh
#!/bin/bash
if test
then
  echo "No expression returns a True"
else
  echo "No expression returns a False"
fi
$ ./test6.sh
No expression returns a False

当你加入一个条件时, test命令会测试该条件。例如,可以使用test命令确定变量中是否有内容。这只需要一个简单的条件表达式。

$ cat test6.sh
#!/bin/bash
my_variable="Full"
if test $my_variable
then
  echo "The $my_variable expression returns a True"
else
  echo "The $my_variable expression returns a False"
fi
$ ./test6.sh
The Full expression returns a True

变量my_variable中包含有内容( Full),因此当test命令测试条件时,返回的退出状态为0。这使得then语句块中的语句得以执行。如果该变量中没有包含内容,就会出现相反的情况:

$ cat test6.sh
#!/bin/bash
my_variable=""
if test $my_variable
then
  echo "The $my_variable expression returns a True"
else
  echo "The $my_variable expression returns a False"
fi
$ ./test6.sh
The expression returns a False

bash shell提供了另一种条件测试方法,无需在if-then语句中声明test命令。
if [ condition ]
then
commands
fi
方括号定义了测试条件。注意,第一个方括号之后和第二个方括号之前必须加上一个空格,否则就会报错。test命令可以判断三类条件:数值比较,字符串比较,文件比较。

数值比较:使用test命令最常见的情形是对两个数值进行比较。

$ cat numeric_test.sh
#!/bin/bash
value1=10
value2=11
if [ $value1 -gt 5 ]#测试变量value1的值是否大于5。
then
  echo "The test value $value1 is greater than 5"
fi
#
if [ $value1 -eq $value2 ]#测试变量value1的值是否和变量value2的值相等。
then
  echo "The values are equal"
else
  echo "The values are different"
fi

两个数值条件测试的结果和预想一致。

$ ./numeric_test.sh
The test value 10 is greater than 5
The values are different

但是涉及浮点值时,数值条件测试会有一个限制。

$ cat floating_point_test.sh
#!/bin/bash
value1=5.555
echo "The test value is $value1"
if [ $value1 -gt 5 ]
then
  echo "The test value $value1 is greater than 5"
fi
$ ./floating_point_test.sh
The test value is 5.555
./floating_point_test.sh: line 8:
[: 5.555: integer expression expected

此例,变量value1中存储的是浮点值。接着,脚本对这个值进行了测试。显然这里出错了。记住, bash shell只能处理整数。说明我们不能在test命令中使用浮点值。

字符串比较
字符串的相等和不等条件不言自明,很容易看出两个字符串值是否相同:

$ cat test7.sh
#!/bin/bash
testuser=rich
if [ $USER = $testuser ]
then
  echo "Welcome $testuser"
fi
$ ./test7.sh
Welcome rich

字符串不等条件也可以判断两个字符串是否有相同的值:

$ cat test8.sh
#!/bin/bash
testuser=baduser
if [ $USER != $testuser ]
then
  echo "This is not $testuser"
else
  echo "Welcome $testuser"
fi
$ ./test8.sh
This is not baduser

在比较字符串的相等性时,比较测试会将所有的标点和大小写情况都考虑在内。
要测试一个字符串是否比另一个字符串大就是麻烦的开始。当要开始使用测试条件的大于或小于功能时,就会出现两个经常困扰shell程序员的问题:
1.大于号和小于号必须转义,否则shell会把它们当作重定向符号,把字符串值当作文件
名;
2.大于和小于顺序和sort命令所采用的不同。
在编写脚本时,第一条可能会导致一个不易察觉的严重问题。下面的例子展示了shell脚本编程初学者时常碰到的问题。

$ cat badtest.sh
#!/bin/bash
val1=baseball
val2=hockey
if [ $val1 > $val2 ]
then
  echo "$val1 is greater than $val2"
else
  echo "$val1 is less than $val2"
fi
$ ./badtest.sh
baseball is greater than hockey
$ ls -l hockey
-rw-r--r-- 1 rich rich 0 Sep 30 19:08 hockey

这个脚本中只用了大于号,没有出现错误,但结果是错的。脚本把大于号解释成了输出重定向(参见第15章)。因此,它创建了一个名为hockey的文件。由于重定向的顺利完成, test命令返回了退出状态码0, if语句便以为所有命令都成功结束了。要解决这个问题,就需要正确转义大于号:

$ cat test9.sh
#!/bin/bash
val1=baseball
val2=hockey
if [ $val1 \> $val2 ]
then
echo "$val1 is greater than $val2"
else
echo "$val1 is less than $val2"
fi
$ ./test9.sh
baseball is less than hockey

现在的答案已经符合预期的了。
第二个问题更细微,除非你经常处理大小写字母,否则几乎遇不到。 sort命令处理大写字母的方法刚好跟test命令相反。让我们在脚本中测试一下这个特性。

$ cat test9b.sh
#!/bin/bash
val1=Testing
val2=testing
if [ $val1 \> $val2 ]
then
  echo "$val1 is greater than $val2"
else
  echo "$val1 is less than $val2"
fi
$ ./test9b.sh
Testing is less than testing
$ sort testfile
testing
Testing

在比较测试中,大写字母被认为是小于小写字母的。但sort命令恰好相反。当你将同样的字符串放进文件中并用sort命令排序时,小写字母会先出现。这是由各个命令使用的排序技术不同造成的。比较测试中使用的是标准的ASCII顺序,根据每个字符的ASCII数值来决定排序结果。 sort命令使用的是系统的本地化语言设置中定义的排序顺序。对于英语,本地化设置指定了在排序顺序中小写字母出现在大写字母前。

-n和-z可以检查一个变量是否含有数据:

$ cat test10.sh
#!/bin/bash
val1=testing
val2=''
if [ -n $val1 ] #判断val1变量是否长度非0,而它的长度正好非0,所以then部分被执行了。
then
  echo "The string '$val1' is not empty"
else
  echo "The string '$val1' is empty"
fi
#
if [ -z $val2 ] #判断val2变量是否长度为0,而它正好长度为0,所以then部分被执行了。
then
  echo "The string '$val2' is empty"
else
  echo "The string '$val2' is not empty"
fi
#
if [ -z $val3 ] #判断val3变量是否长度为0。这个变量并未在shell脚本中定义过,所以它的字符串长度仍然为0,尽管它未被定义过。
then
  echo "The string '$val3' is empty"
else
  echo "The string '$val3' is not empty"
fi
$ ./test10.sh
The string 'testing' is not empty
The string '' is empty
The string '' is empty

文件比较(这里举例了10个不同的比较情况)
1.检查目录
-d测试会检查指定的目录是否存在于系统中。如果你打算将文件写入目录或是准备切换到某个目录中,先进行测试总是件好事情。

$ cat test11.sh
#!/bin/bash
jump_directory=/home/arthur
if [ -d $jump_directory ]
then
  echo "The $jump_directory directory exists"
  cd $jump_directory
  ls
else
  echo "The $jump_directory directory does not exist"
fi
$ ./test11.sh
The /home/arthur directory does not exist

2.检查对象是否存在
-e允许你的脚本代码在使用文件或目录前先检查它们是否存在。

$ cat test12.sh
#!/bin/bash
location=$HOME
file_name="sentinel"
if [ -e $location ]
then #目录存在
  echo "OK on the $location directory."
  echo "Now checking on the file, $file_name."
    if [ -e $location/$file_name ]
    then #文件存在
      echo "OK on the filename"
      echo "Updating Current Date..."
      date >> $location/$file_name
    else #文件不存在
      echo "File does not exist"
      echo "Nothing to update"
    fi
else #目录不存在
  echo "The $location directory does not exist."
  echo "Nothing to update"
fi
$ ./test12.sh
OK on the /home/Christine directory.
Now checking on the file, sentinel.
File does not exist
Nothing to update
$ touch sentinel
$ ./test12.sh
OK on the /home/Christine directory.
Now checking on the file, sentinel.
OK on the filename
Updating Current Date...

第一次检查用-e比较来判断用户是否有HOME目录。如果有,接下来的-e比较会检查sentinel文件是否存在于HOME目录中。如果不存在, shell脚本就会提示该文件不存在,不需要进行更新。为确保更新操作能够正常进行,我们创建了sentinel文件,然后重新运行这个shell脚本。这一次在进行条件测试时, $HOME和sentinel文件都存在,因此当前日期和时间就被追加到了文件中。
3.检查文件
-e比较可用于文件和目录。要确定指定对象为文件,必须用-f比较。

$ cat test13.sh
#!/bin/bash
item_name=$HOME
echo "The item being checked: $item_name"
if [ -e $item_name ]
then #Item存在
  echo "The item, $item_name, does exist."
  echo "But is it a file?"
    if [ -f $item_name ]
    then #Item是个文件
      echo "Yes, $item_name is a file."
    else #Item不是文件
      echo "No, $item_name is not a file."
    fi
else #Item不存在
  echo "The item, $item_name, does not exist."
  echo "Nothing to update"
fi
$ ./test13.sh
The item being checked: /home/Christine
The item, /home/Christine, does exist.
But is it a file?
No, /home/Christine is not a file.

这一小段脚本进行了大量的检查!它首先使用-e比较测试$HOME是否存在。如果存在,继续用-f来测试它是不是一个文件。如果它不是文件(当然不会是了),就会显示一条消息,表明这不是一个文件。
4.检查是否可读

$ cat test14.sh
#!/bin/bash
pwfile=/etc/shadow # 首先检查文件是否存在
if [ -f $pwfile ]
then # 文件存在
  if [ -r $pwfile ]
    then #可读的话
      tail $pwfile
    else #如果不可读
      echo "Sorry, I am unable to read the $pwfile file"
   fi
else #文件不存在
  echo "Sorry, the file $file does not exist"
fi
$ ./test14.sh
Sorry, I am unable to read the /etc/shadow file

/etc/shadow文件含有系统用户加密后的密码,所以它对系统上的普通用户来说是不可读的。-r比较确定该文件不允许进行读取,因此测试失败, bash shell执行了if-then语句的else部分。
5.检查空文件
应该用-s比较来检查文件是否为空,尤其是在不想删除非空文件的时候。要留心的是,当-s比较成功时,说明文件中有数据。

$ cat test15.sh
#!/bin/bash
file_name=$HOME/sentinel
if [ -f $file_name ]
then
    if [ -s $file_name ]
    then
      echo "The $file_name file exists and has data in it."
      echo "Will not remove this file."
    else
      echo "The $file_name file exists, but is empty."
      echo "Deleting empty file..."
      rm $file_name
    fi
else
  echo "File, $file_name, does not exist."
fi
$ ls -l $HOME/sentinel
-rw-rw-r--. 1 Christine Christine 29 Jun 25 05:32 /home/Christine/sentinel
$ ./test15.sh
The /home/Christine/sentinel file exists and has data in it.
Will not remove this file.

-f比较测试首先测试文件是否存在。如果存在,由-s比较来判断该文件是否为空。空文件会被删除。可以从ls –l的输出中看出sentinel并不是空文件,因此脚本并不会删除它。
6.查看文件是否可写
-w比较会判断你对文件是否有可写权限。脚本test16.sh只是脚本test13.sh的修改版。现在不单检查item_name是否存在、是否为文件,还会检查该文件是否有写入权限。

$ cat test16.sh
#!/bin/bash
item_name=$HOME/sentinel
echo "The item being checked: $item_name"
echo "Yes, $item_name is a file."
echo "But is it writable?"
  if [ -w $item_name ]
    then #Item是可写的
      echo "Writing current time to $item_name"
      date +%H%M >> $item_name
    else #Item不可写
      echo "Unable to write to $item_name"
  fi
else #Item不是一个文件
  echo "No, $item_name is not a file."
fi
$ ls -l sentinel
-rw-rw-r--. 1 Christine Christine 0 Jun 27 05:38 sentinel
$ ./test16.sh
The item being checked: /home/Christine/sentinel
Yes, /home/Christine/sentinel is a file.
But is it writable?
Writing current time to /home/Christine/sentinel
$ cat sentinel
0543

变量item_name被设置成HOME/sentinel,该文件允许用户进行写入。因此当脚本运行时, -w测试表达式会返回非零退出状态,然后执行then代码块,将时间戳写入文件sentinel中。如果使用chmod关闭文件sentinel的用户 写入权限, -w测试表达式会返回非零的退出状态码,时间戳不会被写入文件。

$ chmod u-w sentinel
$ ls -l sentinel
-r--rw-r--. 1 Christine Christine 5 Jun 27 05:43 sentinel
$ ./test16.sh
The item being checked: /home/Christine/sentinel
The item, /home/Christine/sentinel, does exist.
But is it a file?
Yes, /home/Christine/sentinel is a file.
But is it writable?
Unable to write to /home/Christine/sentinel

chmod命令可用来为读者再次回授写入权限。这会使得写入测试表达式返回退出状态码0,并允许一次针对文件的写入尝试。
7. 检查文件是否可以执行
-x比较是判断特定文件是否有执行权限的一个简单方法。虽然可能大多数命令用不到它,但如果你要在shell脚本中运行大量脚本,它就能发挥作用。

$ cat test17.sh
#!/bin/bash
if [ -x test16.sh ]
then
  echo "You can run the script: "
  ./test16.sh
else
  echo "Sorry, you are unable to execute the script"
fi
$ ./test17.sh
You can run the script:
[...]
$ chmod u-x test16.sh
$ ./test17.sh
Sorry, you are unable to execute the script

这段示例shell脚本用-x比较来测试是否有权限执行test16.sh脚本。如果有权限,它会运行这个脚本。在首次成功运行test16.sh脚本后,更改文件的权限。这次, -x比较失败了,因为你已经没有test16.sh脚本的执行权限了。
8. 检查所属关系
-O比较可以测试出你是否是文件的属主。

$ cat test18.sh
#!/bin/bash
if [ -O /etc/passwd ]
then
  echo "You are the owner of the /etc/passwd file"
else
  echo "Sorry, you are not the owner of the /etc/passwd file"
fi
$ ./test18.sh
Sorry, you are not the owner of the /etc/passwd file

这段脚本用-O比较来测试运行该脚本的用户是否是/etc/passwd文件的属主。这个脚本是运行在普通用户账户下的,所以测试失败了。
9. 检查默认属组关系
-G比较会检查文件的默认组,如果它匹配了用户的默认组,则测试成功。(这部分我省略了,可以自行看书)
10. 检查文件日期
最后一组方法用来对两个文件的创建日期进行比较。这在编写软件安装脚本时非常有用。有时候,你不会愿意安装一个比系统上已有文件还要旧的文件。
-nt比较会判定一个文件是否比另一个文件新。如果文件较新,那意味着它的文件创建日期更近。 -ot比较会判定一个文件是否比另一个文件旧。如果文件较旧,意味着它的创建日期更早。

$ cat test20.sh
#!/bin/bash
if [ test19.sh -nt test18.sh ]
then
  echo "The test19 file is newer than test18"
else
  echo "The test18 file is newer than test19"
fi
#
if [ test17.sh -ot test19.sh ]
then
  echo "The test17 file is older than the test19 file"
fi
$ ./test20.sh
The test19 file is newer than test18
The test17 file is older than the test19 file
$ ls -l test17.sh test18.sh test19.sh
-rwxrw-r-- 1 rich rich 167 2014-07-30 16:31 test17.sh
-rwxrw-r-- 1 rich rich 185 2014-07-30 17:46 test18.sh
-rwxrw-r-- 1 rich rich 167 2014-07-30 17:50 test19.sh

用于比较文件路径是相对你运行该脚本的目录而言的。如果你要检查的文件已经移走,就会出现问题。另一个问题是,这些比较都不会先检查文件是否存在。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容