bash-shell高级编程--条件判断

条件判断

每一个完整并且合理的程序语言都具有条件判断的功能,并且可以根据条件判断的结果做下一步处理,bash中有test命令,有各种中括号和圆括号操作,和if/then结构

条件测试结构

  • 使用if/then结构判断命令列表的退出码状态是否为0,如果成功的话,那么就执行接下来一个或多个命令。
  • 有一个专有命令[(左括号,特殊字符),这个命令与test命令等价,并且处于效率上的考虑,这是一个内建命令,这个命令把它的参数作为表达式或者文件测试,并且根据比较的结果返回一个退出状态码(0表示真,1表示假)
  • 在版本2.02的bash中,引入了[[....]]扩展测试命令,因为这种表现形式可能对某些语言的程序员来说更熟悉一些,注意[[]]是一个关键字,并不是一个命令,并且bash会将[[ $a -lt $b ]]看成单独的元素,并且返回一个退出状态码。

if命令不仅可以测试中括号中的条件,可以测试任何命令

if cmp a b &> /dev/null # 禁止输出.
then echo "Files a and b are identical."
else echo "Files a and b differ."
fi

# 非常有用的"if-grep"结构:
# ------------------------
if grep -q Bash file
then echo "File contains at least one occurrence of Bash."
fi

word=Linux
letter_sequence=inu
if echo "$word" | grep -q "$letter_sequence"
# "-q" 选项是用来禁止输出的.
then
 echo "$letter_sequence found in $word"
else
 echo "$letter_sequence not found in $word"
fi

## 执行成功,进到if条件判断下
if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
then echo "Command succeeded."
else echo "Command failed."
fi
  • 一个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

什么是真

#!/bin/bash

# 小技巧:
# 如果你不能够确定一个特定的条件该如何进行判断,
#+ 那么就使用if-test结构.
echo

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

echo "Testing \"1\""
if [ 1 ]
# one
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
# 随便的一串字符为真.

echo

echo "Testing \"\$xyz\""
if [ $xyz ]
# 判断$xyz是否为null, 但是...

# 这只是一个未初始化的变量.
then
 echo "Uninitialized variable is true."
else
 echo "Uninitialized variable is false."
fi
# 未定义的初始化为假.

echo

echo "Testing \"-n \$xyz\""
if [ -n "$xyz" ]
# 更加正规的条件检查.
then
 echo "Uninitialized variable is true."
else
 echo "Uninitialized variable is false."
fi
# 未初始化的变量为假.

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
exit 0

执行结果

andrew@andrew:/work/bash/src$ bash if_true.sh 

Testing "0"
0 is true.

Testing "1"
1 is true.

Testing "-1"
-1 is true.

Testing "NULL"
NULL is false.

Testing "xyz"
Random string is true.

Testing "$xyz"
Uninitialized variable is false.

Testing "-n $xyz"
Uninitialized variable is false.

Testing "-n $xyz"
Null variable is false.

Testing "false"
"false" is true.

Testing "$false"
"$false" is false.

如果ifthen在条件判断的同一行上的话,必须使用分号结束if表达式,ifthen都是关键字。关键字(或者命令)如果作为表达式的开头,并且如果想在同一行上再写一个新的表达式的话,那么必须使用分号来结束上一句表达式。

if [ -x "$filename" ]; then

else if 和elif

elifelse if的缩写形式,作用是在外部的判断结构中在嵌入一个内部的if/then结构

if [ condition1 ]
then
  command1
  command2
  command3
elif [ condition2 ]
# 与else if一样
then
  command4
  command5
else
  default-command
fi

if test condition结构与if [ condition ]完全相同。

test命令在Bash中是内建命令, 用来测试文件类型, 或者用来比较字符串. 因此, 在Bash
脚本中, test命令并不会调用外部的 /usr/bin/test 中的test命令, 这是sh-utils工具包中
的一部分. 同样的, [也并不会调用 /usr/bin/[ , 这是 /usr/bin/test 的符号链接.

test/usr/bin/test, [], 和/usr/bin/[都是等价的命令

#!/bin/bash
echo
if test -z "$1"
then
 echo "No command-line arguments."
else
 echo "First command-line argument is $1."
fi
echo
if /usr/bin/test -z "$1"
# 与内建的"test"命令结果相同.
then
 echo "No command-line arguments."
else
 echo "First command-line argument is $1."
fi
echo
if [ -z "$1" ]
# 与上边的代码块作用相同.
#
if [ -z "$1"
应该能够运行, 但是...
#+ Bash报错, 提示缺少关闭条件测试的右中括号.
then
 echo "No command-line arguments."
else
 echo "First command-line argument is $1."
fi
echo
if /usr/bin/[ -z "$1" ]
# 再来一个, 与上边的代码块作用相同.
# if /usr/bin/[ -z "$1"
# 能够工作, 但是还是给出一个错误消息.
#
# 注意:
#在版本3.x的Bash中, 这个bug已经被修正了.
then
 echo "No command-line arguments."
else
 echo "First command-line argument is $1."
fi
echo
exit 0

[[]]之间的所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换

使用[[]]而不是[],能够防止脚本中的许多逻辑错误,比如, &&,||, < , 和>操作符能够正常存在于[[]]条件判断结构中,但是如果出现在[]结构中的话,会报错。

if后面也不一定得跟test命令或者用于条件判断的中括号([]或者[[]])

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

if command结构将会返回command的退出码。

在中括号中的条件判断也不是非if不可,也可以使用列表结构

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

home=/home/bozo
[ -d "$home" ] || echo "$home directory does not exist."

算术测试使用(())

#!/bin/bash
# 算术测试.
# (( ... ))结构可以用来计算并测试算术表达式的结果.
# 退出状态将会与[ ... ]结构完全相反!
(( 0 ))
echo "Exit status of \"(( 0 ))\" is $?."
 (( 1 ))
 echo "Exit status of \"(( 1 ))\" is $?."
(( 5 > 4 ))
echo "Exit status of \"(( 5 > 4 ))\" is $?."
(( 5 > 9 ))
echo "Exit status of \"(( 5 > 9 ))\" is $?."
(( 5 - 5 ))
echo "Exit status of \"(( 5 - 5 ))\" is $?."
(( 5 / 4 ))
echo "Exit status of \"(( 5 / 4 ))\" is $?."
(( 1 / 2 ))
echo "Exit status of \"(( 1 / 2 ))\" is $?."
(( 1 / 0 )) 2>/dev/null
#
echo "Exit status of \"(( 1 / 0 ))\" is $?."
# "2>/dev/null"起了什么作用?
# 如果这句被删除会怎样?
# 尝试删除这句, 然后在运行这个脚本.
exit 0

条件测试操作符

条件成立返回真,调试不成立时返回假
-e
文件存在

-a
文件存在
这个选项的效果与-e相同. 但是它已经被"弃用"了, 并且不鼓励使用.

-f
表示这个文件是一个 一般 文件(并不是目录或者设备文件)

-s
文件大小不为零

-d
表示这是一个目录

-b
表示这是一个块设备(软盘, 光驱, 等等.)

-c
表示这是一个字符设备(键盘, modem, 声卡, 等等.)

-p
这个文件是一个管道

-h
这是一个符号链接

-L
这是一个符号链接

-S
表示这是一个socket

-t
文件(描述符)被关联到一个终端设备上
这个测试选项一般被用来检测脚本中的 stdin ( [终端.-t 0 ] )或者 stdout ( [``-t 1 ] )是否来自于一个

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

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

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

-g
set-group-id(sgid)标记被设置到文件或目录上
如果目录具有 sgid 标记的话, 那么在这个目录下所创建的文件将属于拥有这个目录的用户组, 而
不必是创建这个文件的用户组. 这个特性对于在一个工作组中共享目录非常有用.

-u
set-user-id (suid)标记被设置到文件上,如果一个root用户所拥有的二进制可执行文件设置了 set-user-id 标记位的话, 那么普通用户也会以root权限来运行这个文件. 这对于需要访问系统硬件的执行程序(比如pppd和cdrecord)
常有用. 如果没有suid标志的话, 这些二进制执行程序是不能够被非root用户调用的.

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

对于设置了 suid 标志的文件, 在它的权限列中将会以s 表示.

-k
设置 粘贴位
对于"粘贴位"的一般了解, save-text-mode标志是一个文件权限的特殊类型. 如果文件设置了这个标志, 那么这个文件将会被保存到缓存中, 这样可以提高访问速度. 粘贴位如果设置在目录中, 那么它将限制写权限. 对于设置了粘贴位的文件或目录, 在它们的权限标记列中将会显示 t.
drwxrwxrwt 7 root 1024 May 19 21:26 tmp/
如果用户并不拥有这个设置了粘贴位的目录, 但是他在这个目录下具有写权限, 那么这个用户只能在这个目录下删除自己所拥有的文件. 这将有效的防止用户在一个公共目录中不慎覆盖或者删除别人的文件. 比如说 /tmp 目录. (当然, 目录的所有者或者 root用户可以随意删除或重命名其中的文件.)

-O
判断你是否是文件的拥有者

-G
文件的group-id是否与你的相同

-N
从文件上一次被读取到现在为止, 文件是否被修改过

f1 -nt f2
文件 f1 比文件 f2

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

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

测试断掉的链接文件

#!/bin/bash
# 一个可以测试链接断掉的符号链接的文件,并且可以输出它们指向的文件
# 以便于它们可以把输出提供给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=$@

#编写函数linkchk用来检查传递进来的目录或文件是否是链接,
#并判断这些文件或目录是否存在. 然后打印它们所指向的文件.
#如果传递进来的元素包含子目录,
#那么把子目录也放到linkcheck函数中处理, 这样就达到了递归的目的.
##########
# 如果是软连接,则输出,如果是目录就进行地递归
linkchk () {
    for element in $1/*; do
        [ -h "$element" -a ! -e "$element" ] && echo \"$element\"
        [ -d "$element" ] && linkchk $element
        # 当然, '-h'用来测试符号链接, '-d'用来测试目录.
    done
}
# 把每个传递到脚本的参数都送到linkchk函数中进行处理,
# 检查是否有可用目录. 如果没有, 那么就打印错误消息和
# 使用信息.
################
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
# 创建一个新文件 name
andrew@andrew:/work/bash/src$ touch name
# 为name创建符号链接
andrew@andrew:/work/bash/src$ ln -s name aaa
# 删除name文件, aaa将会变成丢失链接文件的符号链接
andrew@andrew:/work/bash/src$ rm name
# 查看aaa为执行当前目录下的name的符号链接文件
andrew@andrew:/work/bash/src$ ls -l
总用量 44
lrwxrwxrwx 1 andrew andrew    4 2月   1 13:20 aaa -> name
-rwxrwxr-x 1 andrew andrew 8656 1月  30 14:46 a.out
-rw-rw-r-- 1 andrew andrew 1887 2月   1 13:08 broken_link.sh
-rw-rw-r-- 1 andrew andrew  322 1月  29 13:08 echo_unique.sh
-rw-rw-r-- 1 andrew andrew 1513 1月  29 15:55 escape_charater.sh
-rw-rw-r-- 1 andrew andrew  279 1月  30 13:48 exit_example.sh
-rw-rw-r-- 1 andrew andrew  199 2月   1 11:52 if_else_more.sh
-rw-rw-r-- 1 andrew andrew 1946 1月  30 21:03 if_true.sh
-rw-rw-r-- 1 andrew andrew  337 1月  29 14:02 single_quotation_mark.sh
-rw-rw-r-- 1 andrew andrew  864 2月   1 12:00 test.c
# 调用脚本清除当前文件夹中,丢失链接文件的符号链接
andrew@andrew:/work/bash/src$ bash broken_link.sh ./ | xargs rm
andrew@andrew:/work/bash/src$ ls -l
总用量 44
-rwxrwxr-x 1 andrew andrew 8656 1月  30 14:46 a.out
-rw-rw-r-- 1 andrew andrew 1887 2月   1 13:08 broken_link.sh
-rw-rw-r-- 1 andrew andrew  322 1月  29 13:08 echo_unique.sh
-rw-rw-r-- 1 andrew andrew 1513 1月  29 15:55 escape_charater.sh
-rw-rw-r-- 1 andrew andrew  279 1月  30 13:48 exit_example.sh
-rw-rw-r-- 1 andrew andrew  199 2月   1 11:52 if_else_more.sh
-rw-rw-r-- 1 andrew andrew 1946 1月  30 21:03 if_true.sh
-rw-rw-r-- 1 andrew andrew  337 1月  29 14:02 single_quotation_mark.sh
-rw-rw-r-- 1 andrew andrew  864 2月   1 12:00 test.c
  • 在将suid标记设置到二进制可执行文件的时候, 一定要小心. 因为这可能会引发安全漏洞.
    但是suid标记不会影响shell脚本.
  • 在当代UNIX系统中, 文件中已经不使用粘贴位了, 粘贴位只使用在目录中.

其他比较操作符

二元比较操作符,用来比较两个变量或者数字。
整数比较
-eq
等于
if [ "$a" -eq "$b" ]

-ne
不等于
if [ "$a" -ne "$b" ]

-gt
大于
if [ “ a " − g t " a" -gt " 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* ]
# 文件扩展匹配(file globbing)和单词分割有效.
[ "$a" == "z*" ] # 如果$a与z*相等(就是字面意思完全一样), 那么结果为真.

!=
不等号
if [ "$a" != "$b" ]
这个操作符将在[[ … ]]结构中使用模式匹配.

<
小于, 按照ASCII字符进行排序
if [[ "$a" < "$b" ]]
if [ "$a" \< "$b" ]
注意"<"使用在 [ ] 结构中的时候需要被转义.

>

大于, 按照ASCII字符进行排序
if [[ "$a" > "$b" ]]
if [ "$a" \> "$b" ]
注意">"使用在 [ ] 结构中的时候需要被转义.

-z
字符串为"null", 意思就是字符串长度为零

-n
字符串不为"null".

#!/bin/bash
a=4
b=5
# 这里的"a"和"b"既可以被认为是整型也可被认为是字符串.
# 这里在算术比较与字符串比较之间是容易让人产生混淆,
#+ 因为Bash变量并不是强类型的.
# Bash允许对于变量进行整形操作与比较操作.
#+ 但前提是变量中只能包含数字字符.
# 不管怎么样, 还是要小心.
echo
# 按照整数进行比较
if [ "$a" -ne "$b" ]
then
 echo "$a is not equal to $b"
 echo "(arithmetic comparison)"
fi

echo

# 按照字符串进行比较
if [ "$a" != "$b" ]
then
 echo "$a is not equal to $b."
 echo "(string comparison)"
 # "4" != "5"
# ASCII 52 != ASCII 53
fi
# 在这个特定的例子中, "-ne"和"!="都可以.
echo
exit 0

检查字符串是否为NULL

该脚本只在ubuntu 16.04上测试过,在其他系统上使用记得先使用shellcheck对脚本进行语法检查

#!/bin/bash
# str-test.sh: 检查null字符串和未引用的字符串,
#+ but not strings and sealing wax, not to mention cabbages and kings . . .
#+ 但不是字符串和封蜡, 也并没有提到卷心菜和国王. . . ??? (没看懂, rojy bug)

# 使用 if [ ... ]

# 如果字符串并没有被初始化, 那么它里面的值未定义.
# 这种状态被称为"null" (注意这与零值不同).

if [ -n $string1 ]
# $string1 没有被声明和初始化.
 then
 echo "String \"string1\" is not null."
 else
 echo "String \"string1\" is null."
fi
# 错误的结果.
# 显示$string1为非null, 虽然这个变量并没有被初始化.


echo


# 让我们再试一下.

if [ -n "$string1" ] # 这次$string1被引号扩起来了.
then
 echo "String \"string1\" is not null."
else
 echo "String \"string1\" is null."
fi
# 注意一定要将引用的字符放到中括号结构中!33 

echo


if [ $string1 ]
# 这次, 就一个$string1, 什么都不加.
then
 echo "String \"string1\" is not null."
else
 echo "String \"string1\" is null."
fi
# 这种情况运行的非常好.
# [ ] 测试操作符能够独立检查string是否为null.
# 然而, 使用("$string1")是一种非常好的习惯.
#
# 就像Stephane Chazelas所指出的,
#if [ $string1 ]只有一个参数, "]"
#if [ "$string1" ] 有两个参数, 一个是空的"$string1", 另一个是"]"

echo
string1=initialized

if [ $string1 ]
# 再来, 还是只有$string1, 什么都不加.
then
 echo "String \"string1\" is not null."
else
 echo "String \"string1\" is null."
fi
# 再来试一下, 给出了正确的结果.
# 再强调一下, 使用引用的("$string1")还是更好一些, 原因我们上边已经说过了.


string1="a = b"

if [ $string1 ]
# 再来, 还是只有$string1, 什么都不加.
then
 echo "String \"string1\" is not null."
else
 echo "String \"string1\" is null."
fi
# 未引用的"$string1", 这回给出了错误的结果!

exit 0

执行结果

andrew@andrew:/work/bash/src$ bash str_test.sh 
String "string1" is not null.

String "string1" is null.

String "string1" is null.

String "string1" is not null.
String "string1" is null.
#!/bin/bash
# zmore

#使用'more'来查看gzip文件

NOARGS=65
NOTFOUND=66
NOTGZIP=67

if [ $# -eq 0 ] # 与if [ -z "$1" ]效果相同
# (译者注: 上边这句注释有问题), $1是可以存在的, 可以为空, 如: zmore "" arg2 arg3
 then
 echo "Usage: `basename $0` filename" >&2
# 错误消息输出到stderr.
 exit $NOARGS
# 返回65作为脚本的退出状态的值(错误码).
fi

filename=$1

if [ ! -f "$filename" ]
# 将$filename引用起来, 这样允许其中包含空白字符.
then
 echo "File $filename not found!" >&2
# 错误消息输出到stderr.
exit $NOTFOUND
fi

if [ ${filename##*.} != "gz" ]
# 在变量替换中使用中括号结构.
then31  echo "File $1 is not a gzipped file!"
 exit $NOTGZIP
fi

zcat $1 | more

# 使用过滤命令'more.'
# 当然, 如果你愿意, 也可以使用'less'.
exit $?
# 脚本将把管道的退出状态作为返回值.
# 事实上, 也不一定非要加上"exit $?", 因为在任何情况下,
# 脚本都会将最后一条命令的退出状态作为返回值.

-a
逻辑与exp1 -a exp2, 如果表达式exp1exp2都为真的话, 那么结果为真.

-o
逻辑或exp1 -o exp2, 如果表达式exp1exp2中至少有一个为真的话, 那么结果为真.

嵌套的if/then条件测试

可以通过if/then结构来使用嵌套的条件测试,最终的结果和上面使用的&&混合比较操作符的结果是相同的。

if [ condition1 ]
then
 if [ condition2 ]
 then
	do-something # But only if both "condition1" and "condition2" valid.
 fi
fi

检测你对测试知识的掌握

if [ -f $HOME/.Xclients ]; then
 exec $HOME/.Xclients
elif [ -f /etc/X11/xinit/Xclients ]; then
 exec /etc/X11/xinit/Xclients
else
    # 失败后的安全设置. 虽然我们永远都不会走到这来.
    # (我们在Xclients中也提供了相同的机制) 保证它不会被破坏.
    xclock -geometry 100x100-5+5 &
    xterm -geometry 80x50-50+150 &
    if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then

    netscape /usr/share/doc/HTML/index.html &
fi
fi

你可能感兴趣的:(bash-shell高级编程--条件判断)