:测试(if/then)文件测试操作符比较操作符(整数比较字符串比较混合比较)

Posted Dontla

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了:测试(if/then)文件测试操作符比较操作符(整数比较字符串比较混合比较)相关的知识,希望对你有一定的参考价值。

原文:http://shouce.jb51.net/shell/

第一章:Shell编程 第二章:Sha-Bang(#!)开始

第三章:特殊字符

第四章. 变量和参数介绍

第五章:引用““与转义\\ 第六章:退出和退出状态exit

文章目录

第七章:测试

每一个完善的编程语言都应该能测试一个条件。然后依据测试的结果做进一步的动作。Bash有test命令,各种括号及内嵌的操作符,还有if/then结构来完成上面的功能。

7.1. 测试结构 if/then [ [[ ... ]]

一个if/then结构测试一列命令的退出状态是否为0(因为依照惯例,0意味着命令执行成功),如果是0则会执行一个或多个命令。

有一个命令 [ (左方括是特殊字符). 它和test是同义词,因为效率的原因,它被内建在shell里。这个命令的参数是比较表达式或者文件测试,它会返回一个退出状态指示比较的结果(0表示真,1表示假)。

在版本2.02,Bash引入了[[ ... ]] 扩展的测试命令,它使熟悉其他语言中这种比较测试的程序员也能很快熟悉比较操作。注意[[是一个关键字 ,不是一个命令。

Bash把[[ $a -lt $b ]]看成一个返回退出状态的单元。

The (( … )) and let … constructs also return an exit status of 0
if the arithmetic expressions they evaluate expand to a non-zero
value. These arithmetic expansion constructs may therefore be used to
perform arithmetic comparisons.
如果 (( … )) 和 let … 构造计算的算术表达式扩展为非零值,则它们也返回退出状态 0。
因此,这些算术扩展结构可用于执行算术比较。

let "1<2" returns 0 (as "1<2" expands to "1")
(( 0 && 1 )) returns 1 (as "0 && 1" expands to "0")

if 命令不仅能测试由方括号括起来的条件,也能测试任何命令。

#!/bin/bash

if cmp a b &> /dev/null  # 禁止输出.	# &表示正确输出和错误输出
then echo "Files a and b are identical."
else echo "Files a and b differ."
fi

# Files a and b differ.

# 非常有用的"if-grep"组合:
# ----------------------------------- 
#if grep -q Bash file
if grep -q Bash ./scriptname.sh
then echo "File contains at least one occurrence of Bash."
fi

# File contains at least one occurrence of Bash.

word=Linux
letter_sequence=inu
if echo "$word" | grep -q "$letter_sequence"
# 选项"-q"使grep禁止输出. ar: 否则会打印Linux
then
  echo "$letter_sequence found in $word"
else
  echo "$letter_sequence not found in $word"
fi

# inu found in Linux

if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED	# ar:这啥命令啊?
then echo "Command succeeded."
else echo "Command failed."
fi

# ./scriptname.sh:30: COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED: 未找到命令
# Command failed.

一个if/then结构能包含嵌套的比较和测试。(啥意思啊下面的代码。。。。)

if echo "Next *if* is part of the comparison for the first *if*."

  if [[ $comparison = "integer" ]]
    then (( a < b ))
  else
    [[ $a < $b ]]
  fi

then
  echo '$a is less than $b'
fi

谦虚的Stéphane Chazelas解释了"if-test"结构的细节

例子 7-1-1. 事实是什么?(测试true与false)

#!/bin/bash

#  小技巧:
#  如果你不确定某一条件怎么被求值,
#+ 可以用一个if-test结构来测试.

echo

echo "Testing \\"0\\""
if [ 0 ]      # 0
then
  echo "0 is true."
else
  echo "0 is false."
fi            # 0为真.

echo

echo "Testing \\"1\\""
if [ 1 ]      # 1
then
  echo "1 is true."
else
  echo "1 is false."
fi            # 1为真.

echo

echo "Testing \\"-1\\""
if [ -1 ]     # -1
then
  echo "-1 is true."
else
  echo "-1 is false."
fi            # -1为真.

echo

echo "Testing \\"NULL\\""
if [ ]        # NULL (空条件)
then
  echo "NULL is true."
else
  echo "NULL is false."
fi            # NULL为假.

echo

echo "Testing \\"xyz\\""
if [ xyz ]    # 字符串
then
  echo "Random string is true."
else
  echo "Random string is false."
fi            # 任意字符串为true.

echo

echo "Testing \\"\\$xyz\\""
if [ $xyz ]   # 变量$xyz为null值,...
              # 它只是一个未初始化的变量.
then
  echo "Uninitialized variable is true."
else
  echo "Uninitialized variable is false."
fi            # 未初始化的变量为false.

echo

echo "Testing \\"-n \\$xyz\\""
if [ -n "$xyz" ]            # 进一步实验核实.	# -n判断非空
then
  echo "Uninitialized variable is true."
else
  echo "Uninitialized variable is false."
fi            # 未始初化的变量为false.

echo


xyz=          # 已初始化, 但设置成null值.

echo "Testing \\"-n \\$xyz\\""
if [ -n "$xyz" ]
then
  echo "Null variable is true."
else
  echo "Null variable is false."
fi            # Null值变量为假.


echo


# 什么时候"false"为真?

echo "Testing \\"false\\""
if [ "false" ]              #  "false"是一个字符串.
then
  echo "\\"false\\" is true." #+ 它被测试为真.
else
  echo "\\"false\\" is false."
fi            # "false"为真.

echo

echo "Testing \\"\\$false\\""  # 再来,未初始化的变量.
if [ "$false" ]
then
  echo "\\"\\$false\\" is true."
else
  echo "\\"\\$false\\" is false."
fi            # "$false"变量为假.
              # 现在, 我们取得了预期的效果.

#  如果我们测试未初始化的变量"$true"会发生什么?

echo

echo "Testing \\"\\$true\\""  # 再来,未初始化的变量.
if [ "$true" ]
then
  echo "\\"\\$true\\" is true."
else
  echo "\\"\\$true\\" is false."
fi            # "$true" is false.


exit 0

练习. 上面例子 7-1的解释.

#!/bin/bash

if [ condition-true ]
then
   command 1
   command 2
   ...
else
   # 或选的(如果不需要就可去掉).
   # 如果条件测试失败,就在这里加入默认的执行命令.
   command 3
   command 4
   ...
fi

if/then

当if和then在同一行的时候,一个分号(;)必须用在if语句的结尾。if和then都是关键字.关键字(或命令)开始一个语句,如果在同一行开始另一个新语句时,前面一个语句必须用分号(;)结束。

if [ -x "$filename" ]; then

Else if 和 elif

  • elif
    elif是else if的缩写。作用是在一个if/then里嵌入一个内部的if/then结构。
if [ condition1 ]
then
   command1
   command2
   command3
elif [ condition2 ]
# 和else if相同
then
   command4
   command5
else
   default-command
fi

if test condition-true结构与if [ condition-true ]结构

if test condition-true结构是精确等同于if [ condition-true ].如果用[ condition-true ]结构,左方括[ , 是一个调用test命令的标识。右方括]在一个if/test中封闭左方括[,但它不是必须的,不过新一些的Bash版本会要求有。

test内建命令([是链接到/usr/bin/test内建命令的一个符号链接)

内建命令指的就是包含在Bash工具包中的命令, 从字面意思上看就是built in.这主要是考虑到执行效率的问题:内建命令将比外部命令执行的更快, 一部分原因是因为外部命令通常都需要fork出一个单独的进程来执行,另一部分原因是特定的内建命令需要直接访问shell的内核部分.
参考文章:高级Bash脚本编程指南(18):内部命令与内建命令(一)

Bash内建的test命令测试文件类型和比较字符串. 因此,在一个Bash脚本中test语句不必调用外部的/usr/bin/test的二进制文件,这个test程序是sh-utils包的一部分。同样的,[也不调用/usr/bin/[/usr/bin/[是链接到/usr/bin/test一个符号链接。

 bash$ type test
 test is a shell builtin
 bash$ type '['
 [ is a shell builtin
 bash$ type '[['
 [[ is a shell keyword
 bash$ type ']]'
 ]] is a shell keyword
 bash$ type ']'
 bash: type: ]: not found

例子 7-1-2. 等价的测试命令:test,/usr/bin/test,[]和/usr/bin/[

#!/bin/bash

echo

if test -z "$1"	# -z判断是否是空字符串;-n判断是否是非空字符串
then
  echo "No command-line arguments."
else
  echo "First command-line argument is $1."
fi	# No command-line arguments.

echo

if /usr/bin/test -z "$1"      # 和内建的"test"命令一样.
then
  echo "No command-line arguments."
else
  echo "First command-line argument is $1."
fi	# No command-line arguments.

echo

if [ -z "$1" ]                # 和上面代码块的功能一样
#   if [ -z "$1"                应该来说会运行, 但是...
#+  Bash给出错误说少了一个封闭的右方括.
then
  echo "No command-line arguments."
else
  echo "First command-line argument is $1."
fi	# No command-line arguments.

echo


if /usr/bin/[ -z "$1" ]       # 同样和上面的代码块一样.
# if /usr/bin/[ -z "$1"       # 工作, 但还是给出一个错误信息.
#                             # 注意:
#                               这个已经在bash 3.x版本被修补好了。
then
  echo "No command-line arguments."
else
  echo "First command-line argument is $1."
fi	# No command-line arguments.

echo

exit 0

[[]]结构

[[]]结构比Bash版本的[]更通用。它是从ksh88中引进的test命令的扩展。

[[]]之间的所有的字符都不会被文件扩展或是标记分割,但是会有参数引用和命令替换。

#!/bin/bash


 file=/etc/passwd
 
 if [[ -e $file ]]	# -e判断文件是否存在
 then
   echo "Password file exists."
 fi	# Password file exists.

[[ ... ]]测试结构比用[ ... ]更能防止脚本里的许多逻辑错误。比如说,&&,||,<>操作符能在一个[[]]测试里通过,但在[]结构会发生错误。

if COMMAND结构(if后不一定接test命令或test结构([]或是[[]]))

在一个if的后面,不必一定是test命令或是test结构([]或是[[]])。

#!/bin/bash

dir=/home/bozo

if cd "$dir" 2>/dev/null; then   # "2>/dev/null"会隐藏错误的信息.
  echo "Now in $dir."
else
  echo "Can't change to $dir."
fi	# Can't change to /home/bozo.

"if COMMAND"结构会返回COMMAND命令的退出状态码。

list construct结构(不用if也能判断,后面接&&||

同样的,在一个测试方括号里面的条件测试也可以用列表结构(list construct)而不必用if。

#!/bin/bash

var1=20
var2=22

[ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"	
[ "$var1" -ne "$var2" ] || echo "$var1 is equal to $var2"
# 20 is not equal to 22

home=/home/bozo
[ -d "$home" ] || echo "$home directory does not exist."	# -d判断是否是目录
[ -d "$home" ] && echo "$home exist."
# /home/bozo directory does not exist.

(( ))结构(算术测试)

(( ))结构扩展并计算一个算术表达式的值。如果表达式值为0,会返回1或假作为退出状态码。一个非零值的表达式返回一个0或真作为退出状态码。这个结构和先前test命令及[]结构的讨论刚好相反。

例子 7-1-3. 用(( ))进行算术测试(注意计算结果和返回结果是两回事,计算结果为0(false),返回1;计算结果不为0(true),返回0;括号内语句非法直接返回1)

#!/bin/bash

# 算术测试.

# (( ... ))结构会求值并测试该值。
# 退出状态码与[ ... ]结构正好相反!

(( 0 ))							# 结果为0(false),返回1
echo "Exit status of \\"(( 0 ))\\" is $?."         # 1

(( 1 ))							# 结果为1(true),返回0
echo "Exit status of \\"(( 1 ))\\" is $?."         # 0

(( 5 > 4 ))                                      # 真
echo "Exit status of \\"(( 5 > 4 ))\\" is $?."     # 0

(( 5 > 9 ))                                      # 假
echo "Exit status of \\"(( 5 > 9 ))\\" is $?."     # 1

(( 5 - 5 ))                                      # 0
echo "Exit status of \\"(( 5 - 5 ))\\" is $?."     # 1

(( 5 / 4 ))                                      # 除法有效.
echo "Exit status of \\"(( 5 / 4 ))\\" is $?."     # 0

(( 1 / 2 ))                                      # 除法计算结果< 1
echo "Exit status of \\"(( 1 / 2 ))\\" is $?."     # 截取为0.
                                                 # 1

(( 1 / 0 )) 2>/dev/null                          # 除以0的非法计算.
#           ^^^^^^^^^^^
echo "Exit status of \\"(( 1 / 0 ))\\" is $?."     # 1

(( 1 / 0 ))
echo "Exit status of \\"(( 1 / 0 ))\\" is $?."     # 1

# 起了什么作用?
# 如果不要"2>/dev/null"这句会怎么样?
# 试试去掉这句再运行这个脚本.

exit 0

7.2. 文件测试操作符

如果下面的条件成立返回真…

-e(exist)文件存在

-a 文件存在(不赞成使用)

这个和-e的作用一样. 它是不赞成使用的,所以它的用处不大。

-f(file)文件是一个普通文件(不是一个目录或是一个设备文件)

-s(solid?非中空的?)文件大小不为零

-d(directory)文件是一个目录

-b(block device)文件是一个块设备(软盘, 光驱, 等等.)

-c(character device)文件是一个字符设备(键盘, 调制解调器, 声卡, 等等.)

-p(pipeline)文件是一个管道

-h 文件是一个符号链接

-L 文件是一个符号链接

-S 文件是一个socket

-t 文件(描述符)与一个终端设备相关

这个测试选项可以用于检查脚本中是否标准输入 ([ -t 0 ])或标准输出([ -t 1 ])是一个终端.

-r 文件是否可读 (指运行这个测试命令的用户的读权限)

-w 文件是否可写 (指运行这个测试命令的用户的读权限)

-x 文件是否可执行 (指运行这个测试命令的用户的读权限)

-g (group)文件或目录的设置-组-ID(sgid)标记被设置

如果一个目录的sgid标志被设置,在这个目录下创建的文件都属于拥有此目录的用户组,而不必是创建文件的用户所属的组。这个特性对在一个工作组里的同享目录很有用处。

-u (user)文件的设置-用户-ID(suid)标志被设置

一个root用户拥有的二进制执行文件如果设置了设置-用户-ID位(suid)标志普通用户可以以root权限运行。
[1] 这对需要存取系统硬件的执行程序(比如说pppd和cdrecord)很有用。
如果没有设置suid位,则这些二进制执行程序不能由非root的普通用户调用。

-rwsr-xr-t    1 root       178236 Oct  2  2000 /usr/sbin/pppd

被设置了suid标志的文件在权限列中以s标志表示.

-k 粘住位设置(S_ISVTX位)

S_ISVTX位在UNIX尚未使用分页技术的早期版本中被称为“粘住位”,如果一个可执行程序被设置了这一位,那么在该程序第一次被执行并结束时,其程序正文部分的一个副本仍被保存在交换区,这使得下次执行时能够较快的将其装入内存。

Commonly known as the “sticky bit,” the save-text-mode flag is a special type of file permission. If a file has this flag set, that file will be kept in cache memory, for quicker access. [2] If set on a directory, it restricts write permission. Setting the sticky bit adds a t to the permissions on the file or directory listing.

保存文本模式标志通常称为“粘滞位”,是一种特殊类型的文件权限。 如果文件设置了此标志,则该文件将保存在高速缓存中,以便更快地访问。 [2] 如果在目录上设置,它会限制写权限。 设置粘性位会将 t 添加到文件或目录列表的权限中。

drwxrwxrwt    7 root         1024 May 19 21:26 tmp/

If a user does not own a directory that has the sticky bit set, but has write permission in that directory, he can only delete files in it that he owns. This keeps users from inadvertently overwriting or deleting each other’s files in a publicly accessible directory, such as /tmp.

如果用户不拥有设置了粘性位的目录,但在该目录中具有写权限,则他只能删除他拥有的文件。 这可以防止用户在可公开访问的目录(例如 /tmp)中无意覆盖或删除彼此的文件。

-O(owner)你是文件拥有者

-G 你所在组和文件的group-id相同

-N 文件最后一次读后被修改

f1 -nt f2 文件f1比f2新

f1 -ot f2 文件f1比f2旧

f1 -ef f2 文件f1和f2 是相同文件的硬链接

! “非” – 反转上面所有测试的结果(如果没有给出条件则返回真).

例子 7-4. 测试断掉的连接(发现死符号链接并且输出它们的链接文件)(这个例子现在看有一点早啊,,,是不是应该先把语法学完。。。。)

#!/bin/bash
# broken-link.sh
# 由Lee bigelow所写<ligelowbee@yahoo.com>
# 已经征得作者的同意.

#一个用于发现死符号链接并且输出它们的链接文件的纯shell的脚本。
#所以它们能把输出提供给xargs并处理 :)
#例如: broken-link.sh /somedir /someotherdir|xargs rm
#
#下面是更好的发现死符号链接的办法:
#
#find "somedir" -type l -print0|\\
#xargs -r0 file|\\
#grep "broken symbolic"|
#sed -e 's/^\\|: *broken symbolic.*$/"/g'
#
#但这不是纯bash脚本,下面的则是.
#注意: 谨防在/proc文件系统和死循环链接中使用!
##############################################################


#如果没有参数被传递给脚本作为搜索目录,则使用当前目录
#
#
####################
[ $# -eq 0 ] && directorys=`pwd` || directorys=$@

#Setup the function linkchk to check the directory it is passed 
#for files that are links and don't exist, then print them quoted.
#If one of the elements in the directory is a subdirectory then 
#send that send that subdirectory to the linkcheck function.
##########
linkchk () 
    for element in $1/*; do
    [ -h "$element" -a ! -e "$element" ] && echo \\"$element\\"
    [ -d "$element" ] && linkchk $element
    # Of course, '-h' tests for symbolic link, '-d' for directory.
    done


#Send each arg that was passed to the script to the linkchk function
#if it is a valid directoy.  If not, then print the error message
#and usage info.
################
for directory in $directorys; do
    if [ -d $directory ]
	then linkchk $directory
	else 
	    echo "$directory is not a directory"
	    echo "Usage: $0 dir1 dir2 ..."
    fi
done

exit 0

例子 28-1, 例子 10-7, 例子 10-3, 例子 28-3, 和 例子 A-1 也说明了文件测试操作符的用法.


[1] 一定要意识到suid二进制文件可能引起潜在的安全漏洞,并且在shell脚本中suid标志已经不起作用了。

[2] 在现代UNIX操作系统,粘住位已经不在文件中再使用,只用在目录中。

7.3. 其它比较操作符

二元比较操作符比较两个变量或是数值。注意整数和字符串比较的分别。

整数比较

-eq 等于

if [ "$a" -eq "$b" ]

-ne 不等于

if [ "$a" -ne "$b" ]

-gt 大于

if [ "$a" -gt "$b" ]

-ge 大于等于

if [ "$a" -ge "$b" ]

-lt 小于

if [ "$a" -lt "$b" ]

-le 小于等于

if [ "$a" -le "$b" ]

< 小于(在双括号里使用)

(("$a" < "$b"))

<= 小于等于 (在双括号里使用)

(("$a" <= "$b"))

> 大于 (在双括号里使用)

(("$a" > "$b"))

>= 大于等于(在双括号里使用)

(("$a" >= "$b"))

字符串比较

= 等于

if [ "$a" = "$b" ]

== 等于(= 的同义词)

if [ "$a" == "$b" ]

它和=是同义词。

==比较操作符在一个双方括号测试和一个单方括号号里意思不同。

[[ $a == z* ]]    # 如果变量$a以字符"z"开始(模式匹配)则为真.
[[ $a == "z*" ]]  # 如果变量$a与z*(字面上的匹配)相等则为真.

[ $a == z* ]      # 文件扩展和单元分割有效.	# ar:什么意思???
[ "$a" == "z*" ]  # 如果变量$a与z*(字面上的匹配)相等则为真.

7 # 多谢Stéphane Chazelas

!= 不相等

if [ "$a" != "$b" ]

操作符在[[ … ]]结构里使用模式匹配.

< 小于,依照ASCII字符排列顺序

if [[ "$a" < "$b" ]]

if [ "$a" \\< "$b" ]

注意"<"字符在[ ] 结构里需要转义

> 大于,依照ASCII字符排列顺序

if [[ "$a" > "$b" ]]

以上是关于:测试(if/then)文件测试操作符比较操作符(整数比较字符串比较混合比较)的主要内容,如果未能解决你的问题,请参考以下文章

Linux的常用测试比较操作符

Linux的常用测试比较操作符

Linux的常用测试比较操作符

(转)shell脚本之文件测试操作符及整数比较符

求Bash Shell脚本,判定文件是不是存在。

Shell脚本 文本测试整数值比较字符串比较 条件测试操作 if语句应用实例