shell结构化命令——test命令

到目前为止,我们在if语句行中看到的都是普通的shell命令。你可能想知道if-then语句能否测试命令退出状态码之外的条件。

答案是不能。但是,在bash shell中有个好用的工具可以帮你使用if-then语句测试其他条件。

test命令可以在if-then语句中测试不同的条件。如果test命令中列出的条件成立,那么test命令就会退出并返回退出状态码0。这样if-then语句的工作方式就和其他编程语言中的if-then语句差不多了。如果条件不成立,那么test命令就会退出并返回非0的退出状态码,这使得if-then语句不会再被执行。

test命令的格式非常简单:

test  condition

condition是test命令要测试的一系列参数和值。当用在if-then语句中时,test命令看起来如下所示:

if  test  condition

then

        commands

fi

如果不写test命令的condition部分,则它会以非0的退出状态码退出并执行else代码块语句:

$ cat test6.sh
#!/bin/bash
# testing the test command
#
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
# testing if a variable has content
#
my_variable="Full"
#
if test $my_variable
then
    echo "The my_variable variable has content and returns a True."
    echo "The my_variable variable content is: $my_variable"
else
    echo "The my_variable variable doesn't have content,"
    echo "and returns a False."
fi
$
$ ./test6.sh
The my_variable variable has content and returns a True.
The my_variable variable content is: Full

由于变量my_variable中包含内容(Full),因此当test命令测试条件时,返回的退出状态码为0。这使得then语句块中的语句得以执行。

如你所料,如果该变量中没有包含内容,就会出现相反的情况: 

$ cat test6.sh
#!/bin/bash
# testing if a variable has content
#
my_variable=""
#
if test $my_variable
then
    echo "The my_variable variable has content and returns a True."
    echo "The my_variable variable content is: $my_variable"
else
    echo "The my_variable variable doesn't have content,"
    echo "and returns a False."
fi
$
$ ./test6.sh
The my_variable variable doesn't have content,
and returns a False.

bash shell提供了另一种条件测试方式,无须在if-then语句中写明test命令:

if  [ condition ]

then

        commands

fi

方括号定义了测试条件。注意,第一个方括号之后和第二个方括号之前必须留有空格,否则就会报错。 

test命令和测试条件可以判断3类条件。

  • 数值比较
  • 字符串比较
  • 文件比较

接下来将介绍如何在if-then语句中使用这些测试条件。

数值比较

使用test命令最常见的情形是对两个数值进行比较。表列出了测试两个值时可用的条件参数。

test命令的数值比较功能
比较描述
n1 -eq n2检查n1是否等于n2
n1 -ge n2检查n1是否大于或等于n2
n1 -gt n2检查n1是否大于n2
n1 -le n2检查n1是否小于或等于n2
n1 -lt n2检查n1是否小于n2
n1 -ne n2检查n1是否不等于n2

数值条件测试可用于数字和变量。来看一个例子:

$ cat numeric_test.sh
#!/bin/bash
# Using numeric test evaluations
#
value1=10
value2=11
#
if [ $value1 -gt 5 ]
then
    echo "The test value $value1 is greater than 5."
fi
#
if [ $value1 -eq $value2 ]
then
    echo "The values are equal."
else
    echo "The values are different."
fi

第一个条件测试会测试变量value1的值是否大于5。第二个条件测试会测试变量value1的值是否和变量value2的值相等。这两个数值条件测试的结果和预想中的一样。

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

警告        对于条件测试,bash shell只能处理整数。尽管可以将浮点值用于某些命令(比如echo),但它们在条件测试下无法正常工作。 

字符串比较

 条件测试还允许比较字符串值。你马上就会看到,比较字符串值比较麻烦。表列出了可用的字符串比较功能。

test命令的字符串比较功能
比较描述
str1 = str2检查str1是否和str2相同
str1 != str2检查str1是否和str2不同

str1 < str2

检查str1是否小于str2
str1 > str2检查str1是否大于str2
-n str1检查str1的长度是否不为0
-z str1检查str1的长度是否为0

 下面将介绍不同的字符串比较功能。

1.字符串相等性

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

$ cat string_test.sh
#!/bin/bash
# Using string test evaluations
#
testuser=christine
#
if [ $testuser = christine ]
then
    echo "The testuser variable contains: christine"
else
    echo "The testuser variable contains: $testuser"
fi
$
$ ./string_test.sh
The testuser variable contains: christine

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

$ cat string_not_test.sh
#!/bin/bash
# Using string test not equal evaluations
#
testuser=rich
#
if [ $testuser != christine ]
then
    echo "The testuser variable does NOT contain: christine"
else
    echo "The testuser variable contains: christine"
fi
$
$ ./string_not_test.sh
The testuser variable does NOT contain: christine

记住,在比较字符串的相等性时,比较测试会将所有的标点和大小写情况都考虑在内。

2.字符串顺序 

要测试一个字符串是否大于或小于另一个字符串就开始变得棘手了。使用测试条件的大于或小于功能时,会出现两个经常困扰shell程序员的问题。

  • 大于号和小于号必须转义,否则shell会将其视为重定向符,将字符串值当作文件名。
  • 大于和小于顺序与sort命令所采用的不同。

在编写脚本时,第一个问题可能会导致不易察觉的严重后果。下面的例子展示了shell脚本编程初学者不时会碰到的状况:

$ cat bad_string_comparison.sh
#!/bin/bash
# Misusing string comparisons
#
string1=soccer
string2=zorbfootball
#
if [ $string1 > $string2 ]
then
    echo "$string1 is greater than $string2"
else
    echo "$string1 is less than $string2"
fi
$
$ ./bad_string_comparison.sh
soccer is greater than zorbfootball
$
$ ls z*
zorbfootball

这个脚本中只用了大于号,虽然没有出现错误,但结果不对。脚本把大于号解释成了输出重定向,因此创建了一个名为zorbfootball的文件。由于重定向顺利完成了,测试条件返回了退出状态码0,if语句便认为条件成立。

要解决这个问题,需要使用反斜线(\)正确地转义大于号:

$ cat good_string_comparison.sh
#!/bin/bash
# properly using string comparisons
#
string1=soccer
string2=zorbfootball
#
if [ $string1 \> $string2 ]
then
    echo "$string1 is greater than $string2"
else
    echo "$string1 is less than $string2"
fi
$
$ rm -i zorbfootball
rm: remove regular empty file 'zorbfootball'? y
$
$ ./good_string_comparison.sh
soccer is less than zorbfootball
$
$ ls z*
ls: cannot access 'z*': No such file or directory

现在的答案才是我们想要的。

注意        字符串soccer小于zorbfootball,因为在比较的时候使用的是每个字符的Unicode编码值。小写字母s的编码值是115,而z的编码值是122。因此,s小于z,进而,soccer小于zorbfootball。 

 第二个问题更细微,除非经常处理大小写字母,否则几乎遇不到。sort命令处理大写字母的方法刚好与test命令相反:

$ cat SportsFile.txt
Soccer
soccer
$
$ sort SportsFile.txt
soccer
Soccer
$
$ cat sort_order_comparison.sh
#!/bin/bash
# Testing string sort order
#
string1=Soccer
string2=soccer
#
if [ $string1 \> $string2 ]
then
    echo "$string1 is greater than $string2"
else
    echo "$string1 is less than $string2"
fi
$
$ ./sort_order_comparison.sh
Soccer is less than soccer

在比较测试中,大写字母被认为是小于小写字母的。但sort命令正好相反。当你将同样的字符串放进文件中并用sort命令排序时,小写字母会先出现。这是由于各个命令使用了不同的排序技术。

比较测试中使用的是标准的Unicode顺序,根据每个字符的Unicode编码值来决定排序结果。sort命令使用的是系统的语言环境设置中定义的排序顺序。对于英语,语言环境设置指定了在排序顺序中小写字母出现在大写字母之前。 

注意        test命令和测试表达式使用标准的数学比较符号来表示字符串比较,而用文本代码来表示数值比较。这个细微的特性被很多程序员理解反了。如果你对数值使用了数学运算符号,那么shell会将它们当成字符串值,并可能产生错误结果。

3.字符串大小 

-n 和-z可以很方便地用于检查一个变量是否为空:

$ cat variable_content_eval.sh
#!/bin/bash
# Testing string length
#
string1=Soccer
string2=''
#
if [ -n $string1 ]
then
    echo "The string '$string1' is NOT empty"
else
    echo "The string '$string1' IS empty"
fi
#
if [ -z $string2 ]
then
    echo "The string '$string2' IS empty"
else
    echo "The string '$string2' is NOT empty"
fi
#
if [ -z $string3 ] 
then
    echo "The string '$string3' IS empty"
else
    echo "The string '$string3' is NOT empty"
fi
$
$ ./variable_content_eval.sh
The string 'Soccer' is NOT empty
The string '' IS empty
The string '' IS empty

这个例子创建了两个字符串变量。string1变量包含了一个字符串,string2变量包含的是一个空串。后续的比较如下:

 if  [ -n "$string1" ]

上述代码用于判断string1变量的长度是否不为0。因为的确不为0,所以执行then部分。

if  [ -z "$string2" ]

上述代码用于判断string2变量的长度是否为0。因为的确为0,所以执行then部分。

if  [ -z "$string3" ]

上述代码用于判断string3变量的长度是否为0。shell脚本中并未定义该变量,所以长度可视为0,尽管它未被定义过。

警告        空变量和未初始化的变量会对shell脚本测试造成灾难性的影响。如果不确定变量的内容,那么最好在将其用于数值或字符串比较之前先通过-n或-z来测试一下变量是否为空。

文件比较 

最后一类比较测试很有可能是shell编程中最为强大且用得最多的比较形式。它允许测试Linux文件系统中文件和目录的状态。下表列出了这类比较。

test命令的文件比较功能
比较描述
-d file检查file是否存在且为目录
-e file检查file是否存在
-f file检查file是否存在且为文件
-r file检查file是否存在且可读
-s file检查file是否存在且非空
-w file检查file是否存在且可写
-x file检查file是否存在且可执行
-O file检查file是否存在且属当前用户所有
-G file检查file是否存在且默认组与当前用户相同
file1 -nt file2检查file1是否比file2新
file1 -ot file2检查file1是否比file2旧

这些测试条件使你能够在shell脚本中检查文件系统中的文件。它们经常出现在需要进行文件访问的脚本中。鉴于其应用广泛,下面来逐个讲解。

1.检查目录

-d测试会检查指定的目录是否存在于系统中。如果打算将文件写入目录或是准备切换到某个目录,那么先测试一下总是件好事:

$ cat jump_point.sh
#!/bin/bash
# Look before you leap
#
jump_directory=/home/Torfa
#
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
$
$ ./jump_point.sh
The /home/Torfa directory does NOT exist.

示例代码使用了-d测试来检查jump_directory变量中的目录是否存在。如果存在,就使用cd命令切换到该目录并列出其中的内容;如果不存在,则输出一条警告信息,然后退出。

2.检查对象是否存在

 -e测试允许在使用文件或目录前先检查其是否存在:

$ cat update_file.sh
#!/bin/bash
# Check if either a directory or file exists
#
location=$HOME
file_name="sentinel"
#
if [ -d $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 file, $file_name."
        echo "Updating file's contents.
        date >> $location/$file_name
    #
    else
        echo "File, $location/$file_name, does NOT exist."
        echo "Nothing to update."
    fi
#
else
    echo "Directory, $location, does NOT exist."
    echo "Nothing to update."
fi
$
$ ./update_file.sh
OK on the /home/christine directory
Now checking on the file, sentinel...
File, /home/christine/sentinel, does NOT exist.
Nothing to update.
$
$ touch /home/christine/sentinel
$
$ ./update_file.sh
OK on the /home/christine directory
Now checking on the file, sentinel...
OK on the file, sentinel.
Updating file's contents.

 首先使用-e测试检查用户的$HOME目录是否存在。如果存在,那么接下来的-e测试会检查sentinel文件是否存在于$HOME目录中。如果文件不存在,则shell脚本会提示该文件缺失,不需要进行更新。

为了确保更新正常进行,我们创建了sentinel文件,然后重新运行这个脚本。这一次在进行条件测试时,$HOME和sentinel文件都存在,因此当前日期和时间就被追加到了文件中。

3.检查文件

-e测试可用于文件和目录。如果要确定指定对象为文件,那就必须使用-f测试:

$ cat dir-or-file.sh
#!/bin/bash
# Check if object exists and is a directory or a file
#
object_name=$HOME
echo
echo "The object being checked: $object_name"
echo
#
if [ -e $object_name ]
then
    echo "The object, $object_name, does exist,"
    #
    if [ -f $object_name ]
    then
        echo "and $object_name is a file."
    else
        echo "and $object_name is a dirctory."
    fi
#
else
    echo "The object, $object_name, does NOT exist."
fi
$
$ ./dir-or-file.sh

The object being checked: /home/christine

The object, /home/christine, does exist,
and /home/christine is a dirctory.

首先,脚本会使用-e测试检查$HOME是否存在。如果存在,就接着用-f测试检查其是否为文件。如果不是文件(当然不会是文件),则显示消息,说明这是目录。

下面对变量object_name略作修改,将目录$HOME替换成文件$HOME/sentinel,结果可就不一样了:

$ nano dir-or-file.sh
$
$ cat dir-or-file.sh
#!/bin/bash
# Check if object exists and is a directory or a file
#
object_name=$HOME/sentinel
echo
echo "The object being checked: $object_name
echo
#
if [ -e $object_name ]
then
    echo "The object, $object_name, does exist,"
    #
    if [ -f $object_name ]
    then
        echo "and $object_name is a file."
    #
    else
        echo "and $object_name is a directory."
    fi
#
else
    echo "The object, $object_name, does NOT exist."
fi
$
$ ./dir-or-file.sh

The object being checked: /home/christine/sentinel

The object, /home/christine/sentinel, does exist,
and /home/christine/sentinel is a file.

当运行该脚本时,对$HOME/sentinel进行的-f测试所返回的退出状态码为0,then语句得以执行,然后输出消息and /home/christine/sentinel is a file。

4.检查是否可读 

在尝试从文件中读取数据之前,最好先使用-r测试检查一下文件是否可读:

$ cat can-I-read-it.sh
#!/bin/bash
# Check if you can read a file
#
pwfile=/etc/shadow
echo
echo "Checking if you can read $pwfile..."
#
# Check if file exists and is a file.
#
if [ -f $pwfile ]
then
    # File does exist. Check if can read it.
    #
    if [ -r $pwfile ]
    then
        echo "Displaying end of file..."
        tail $pwfile
    #
    else
        echo "Sorry, read access to $pwfile is denied."
    fi
#
else
    echo "Sorry, the $pwfile file does not exist."
fi
$
$ ./can-I-read-it.sh

Checking if you can read /etc/shadow...
Sorry, read access to /etc/shadow is denied.

/etc/shadow文件包含系统用户经过加密后的密码,所以普通用户是无法读取该文件的。-r测试判断出了该文件不允许读取,因此测试失败,bash shell执行了if-then语句的else部分。

5.检查空文件

 应该用-s测试检查文件是否为空,尤其是当你不想删除非空文件时。要当心,如果-s测试成功,则说明文件中有数据:

$ cat is-it-empty.sh
#!/bin/bash
# Check if a file is empty
#
file_name=$HOME/sentinel
echo
echo "Checking if $file_name file is empty..."
echo
#
#Check if file exists and is a file.
#
if [ -f $file_name ]
then
    # File does exist. Check if it is empty.
    #
    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 exits, but is empty."
        echo Deleting empty file..."
        rm $file_name
    fi
#
else
    echo "The $file_name file does not exist."
fi
$
$ ls -sh $HOME/sentinel
4.0K /home/christine/sentinel
$
$ ./is-it-empty.sh

Checking if /home/christine/sentinel file is empty...

The /home/christine/sentinel file exists and has data in it.
Will not remove this file.

-f测试可以检查文件是否存在。如果存在,就使用-s测试来判断该文件是否为空。空文件会被删除。你可以从ls -sh的输出中看出sentinel并不是空文件(4.0 K),因此脚本并不会删除它。

6.检查是否可写 

-w测试可以检查是否对文件拥有可写权限。脚本can-I-write-to-it.sh只是脚本can-I-read-it.sh的修改版。现在,该脚本不再检查是否可以读取item_name文件,而是检查是否有权写入该文件:

$ cat can-I-write-it.sh
#!/bin/bash
# Check if a file is writable
#
item_name=$HOME/sentinel
echo
echo "Checking if you can write to $item_name..."
#
# Check if file exists and is a file.
#
if [ -f $item_name ]
then
    # File does exist. Check if can write to it.
    #
    if [ -r $item_name ]
    then
        echo "Writing current time to $item_name"
        date +%H%M >> $item_name
    #
    else
        echo "Sorry, write access to $item_name is denied."
    fi
#
else
    echo "Sorry, the $item_name does not exist."
fi
$
$ ls -o $HOME/sentinel
-rw-rw-r-- 1 christine 32 Jan 15 17:08 /home/christine/sentinel
$
$ ./can-I-write-to-it.sh

Checking if you can write to /home/christine/sentinel...
Writing current time to /home/christine/sentinel

 变量item_name被设置成了$HOME/sentinel,该文件允许用户写入。因此,运行该脚本时,-w测试会返回值为0的退出状态码,然后执行then代码块,将时间戳写入文件sentinel中。

如果使用chmod去掉文件sentinel的用户写入权限,那么-w测试会返回非0的退出状态码,时间戳则不会被写入文件:

$ chmod u-w $HOME/sentinel
$
$ ls -o $HOME/sentinel
-r--rw-r-- 1 christine 37 Jan 15 12:07 /home/christine/sentinel
$
$ ./can-I-write-to-it.sh

Checking if you can write to /home/christine/sentinel...
Sorry, write access to /home/christine/sentinel is denied.

chmod命令还可以再次将写权限授予用户。这又能使得-w测试返回退出状态码0,允许写入文件。

7.检查文件是否可以执行 

-x测试可以方便地判断文件是否有执行权限。虽然可能大多数命令用不到它,但如果想在shell脚本中运行大量程序,那就得靠它了:

$ cat can-I-run-it.sh
#!/bin/bash
# Check if you can run a file
#
item_name=$HOME/scripts/can-I-write-to-it.sh
echo
echo "Checking if you can run $item_name..."
#
# Check if file is executable.
#
if [ -x $item_name ]
then
    echo "You can run $item_name."
    echo "Running $item_name..."
    $item_name
#
else
    echo "Sorry, you cannot run $item_name."
#
fi
$
$ ./can-I-run-it.sh
Checking if you can run /home/christine/scripts/can-I-run-it.sh...
You can run /home/christine/scripts/can-I-write-to-it.sh.
Running /home/christine/scripts/can-I-write-to-it.sh...
[...]
$
$ chmod u-x can-I-write-to-it.sh
$
$ ./can-I-run-it.sh

Checking if you run /home/christine/scripts/can-I-write-to-it.sh...
Sorry, you cannot run /home/christine/scripts/can-I-write-to-it.sh.

这段shell脚本使用-x测试来检查是否有权限执行can-I-write-to-it.sh脚本。如果有权限,就运行该脚本。在首次成功运行can-I-write-to-it.sh脚本后,更改文件权限。这次,-x测试失败了,因为你已经没有can-I-write-to-it.sh脚本的执行权限了。

8.检查所有权 

-O测试可以轻松地检查你是否是文件的属主:

$ cat do-I-own-it.sh
#!/bin/bash
# Check if you own a file
#
if [ -O /etc/passwd ]
then
    echo "You are the owner of the /etc/passwd file."
#
else
    echo "Sorry, you are NOT /etc/passwd file's owner."
#
fi
$
$ whoami
christine
$
$ ls -O /etc/passwd
-rw-r--r-- 1 root 2842 Apr 23 15:25 /etc/passwd
$
$ ./do-I-own-it.sh
Sorry, you are NOT /etc/passwd file's owner.

该脚本使用-O测试来检查运行脚本的用户是否是/etc/passwd文件的属主。由于脚本是以普通用户身份运行的,因此测试失败了。

9.检查默认属组关系 

-G测试可以检查文件的属组,如果与用户的默认组匹配,则测试成功。-G只会检查默认组而非用户所属的所有组,这会让人有点儿困惑。来看一个例子:

$ cat check_default_group.sh
#!/bin/bash
# Compare file and script user's default groups
#
if [ -G $HOME/TestGroupFile ]
then
    echo "You are in the same default group as"
    echo "the $HOME/TestGroupFile file's group."
#
else
    echo "Sorry, your default group and $HOME/TestGroupFile"
    echo "file's group are different."
#
fi
$
$ touch $HOME/TestGroupFile
$
$ ls -g $HOME/TestGroupFile
-rw-rw-r-- 1 christine 0 Jan 15 13:58 /home/christine/TestGroupFile
$
$ ./check_default_group.sh
You are in the same default group as
the /home/christine/TestGroupFile file's group.
$
$ groups
christine adm cdrom sudo dip plugdev lpadmin lxd sambashare
$
$ chgrp adm $HOME/TestGroupFile
$
$ ls -g $HOME/TestGroupFile
-rw-rw-r-- 1 adm 0 Jan 15 13:58 /home/christine/TestGroupFile
$
$ ./check_default_group.sh
Sorry, your default group and /home/christine/TestGroupFile
file's group are different.

 第一次运行脚本时,$HOME/TestGroupFile文件属于christine组,所以通过了-G比较。接下来,组被改成了adm组,用户也是其中的一员。但-G比较失败了,因为它只会比较默认组,不会比较其他的组。

10.检查文件日期

 最后一组测试用来比较两个文件的创建日期。这在编写软件安装脚本时非常有用。有时,你可不想安装一个比系统中已有文件还要旧的文件。

-nt测试会判定一个文件是否比另一个文件更新。如果文件较新,那意味着其文件创建日期更晚。-ot测试会判定一个文件是否比另一个文件更旧。如果文件较旧,则意味着其文件创建日期更早:

$ cat check_file_dates.sh
#!/bin/bash
# Compare two file's creation dates/times
#
if [ $HOME/Downloads/games.rpm -nt $HOME/software/games.rpm ]
then
    echo "The $HOME/Downloads/games.rpm file is newer"
    echo "than the $HOME/software/games.rpm file."
#
else
    echo "The $HOME/Downloads/games.rpm file is older"
    echo "than the $HOME/software/games.rpm file."
#
fi
$
$ ./check_file_dates.sh
The /home/christine/Downloads/games.rpm file is newer
than the /home/christine/software/games.rpm file.

在脚本中,这两种测试都不会先检查文件是否存在。这是一个问题。试试下面的测试:

$ rm $HOME/Downloads/games.rpm
$
$ ./check_file_dates.sh
The /homw/christine/Downloads/games.rpm file is older
than the /home/christine/software/games.rpm file.

这个小示例展示了如果有其中一个文件不存在,那么-nt测试返回的信息就不正确。在-nt或-ot测试之前,务必确保文件存在。 

  • 53
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值