# 善用判断

第十章 中,提到过 $? 这个变量所代表的含义,以及通过 && 和 || 来判定前一个指令执行回传值对于后一个指令是否要进行的依据。

在第十章中,判定一个目录是否存在,使用了 ll 目录 && 执行指令 的方式来判定 xx 目录是否存在,从而决定后续指令是否执行,但是有更简单的方式进行条件判断,就是通过 test 指令

# 使用 test 指令的测试功能

test 指令主要用于检测文件或相关属性时的指令和比较值,比如检查 /mrcode 是否存在时

# -e 是检测文件是否存在的选项
[root@cloud-08 script]# test -e /mrcode
1
2

上面命令没有任何输出值

[root@cloud-08 script]# test -e /mrcode && echo "exist" || "not exist"
exist
# 通过与 && 或 || 可以知道是存在还是不存在了
1
2
3

要善用 man 查看该指令的信息,下面是整理翻译出来的其他选项

关于某个文件名的 文件类型 判断。如 test -e filename 标识是否存在

测试的标志 含义
-e 文件是否存在;常用
-f 该文件是否存在且为文件(file)?常用
-d 该文件是否存在且为目录(directory)?常用
-b 该文件是否存在且为一个 block device 装置?
-c 该文件是否存在且为一个 character device 装置?
-S 该文件是否存在且为一个 Socket 文件?
-p 该文件是否存在且为一个 FIFO(pipe)文件?
-L 该文件是否存在且为一个连接文件?

关于文件的 权限 判定。如 test -r filename 标识是否可读?(但 root 权限常有例外)

测试的标志 含义
-r 该文件是否存在且具有可读权限?
-w 该文件是否存在且具有可写权限?
-x 该文件是否存在且具有可执行权限?
-u 该文件是否存在且具有 SUID 属性?
-g 该文件是否存在且具有 SGID 属性?
-k 该文件是否存在且具有 Sticky bit 属性?
-s 该文件是否存在且为「非空白文件」?

两个文件之间的比较。如 test file1 -nt file2

测试的标志 含义
-nt (newer than)判断 file1 是否比 file2 新
-ot (older than)判断 file1 是否比 file2 旧
-ef 判断 file1 与 file2 是否是同一文件,可用在判断 hard link 的判定上。主要意义在判定两个文件是否均指向同一个 inode

两个整数之间的判定。test nl -eq n2

测试的标志 含义
-eq 两数值相等(equal)
-ne 不相等(not equal)
-gt 大于(greater than)
-lt 小于(less than)
-ge 大于等于(greater than or equal)
-le 小于等于(less than or equal)

判定字符串的数据

测试的标志 含义
test -z string 判定字符串是否为 0?若为空串,则为 true
test -n string 判定字符串是否不为 0?若为空串,则为 false;注意:-n 可省略
test str1 == str2 是否相等,相等则为 true
test str1 != str2 是否不相等,相等则为 false

多重条件判断。比如 test -r filename -a -x filename

测试的标志 含义
-a (and)两状况同时成立;如:test -r filename -a -x filename,则 file 同时具有 r 与 x 权限时才为 true
-o (or)任意一个成立。如:test -r filename -o -x filename,则 file 具有 r 或 x 权限时就为 true
! 反向状态。

总结完这么多的判定,就可以来写几个简单的例子。让用户输入一个文件名,我们判断:

  1. 该文件是否存在,若不存在则给予一个「Filename does not exist」 提示,并中断程序
  2. 若该文件存在,则判断是文件还是目录:文件输出「Filename is regular file」,目录输出 「Filename is directory」
  3. 判断执行者的身份对这个文件或目录所拥有的权限,并输出权限数据

下面是笔者写的思路,代码组织方面有点糟糕。还有指令使用不太熟悉

#!/bin/bash
# Program
# 
# History
#       2020/01/19              mrcode          first relese
read -p "请输入一个文件名:" filename
# 判断是否输入了字符串
test -z ${filename} && echo "请输入一个有效的文件名!" && exit -1

# 判断该文件是否存在: 不存在输出提示信息并退出
# 特别是这里的多条指令的执行,使用 || 会很难处理,只能转成 true
test ! -e ${filename} && echo "${filename} does not exist" && exit -1

# 提示是文件还是目录
test -f ${filename} && echo "${filename} is regular file" || echo "${filename} is directory"

# 判断执行者的身份对这个文件拥有的权限,并输出
test -r ${filename} && echo "${filename} 可读"
test -w ${filename} && echo "${filename} 可写"
test -x ${filename} && echo "${filename} 可执行"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

测试输出如下

[mrcode@study bin]$ ./file_perm.sh 
请输入一个文件名:ss
ss does not exist
[mrcode@study bin]$ ./file_perm.sh 
请输入一个文件名:/etc
/etc is directory
/etc 可读
/etc 可执行
1
2
3
4
5
6
7
8

书上代码如下

vim file_perm.sh
#!/bin/bash
# Program
#   User input a filename,program will check the flowing:
#	1.) exist?
#	2.) file/directory?
#	3.) file permissions
# History
#       2020/01/19              mrcode          first relese
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

echo -e "Please input a filename,I will check the filename's type and permission. \n\n"
read -p "Input a filename :" filename
# 判断是否输入了字符串
test -z ${filename} && echo "You MUST input a filename. " && exit 0

# 判断该文件是否存在: 不存在输出提示信息并退出
test ! -e ${filename} && echo "The filename ${filename} does not exist" && exit 0

# 开始判断文件类型与属性
test -f ${filename} && filetype="regulare file"
test -d ${filename} && filetype="directory"
test -r ${filename} && perm="readable"
test -w ${filename} && perm="${perm} writable"
test -x ${filename} && perm="${perm} executable"

# 信息输出
echo "The filename: ${filename} is a ${filetype}"
echo "And the permissions for you are : ${perm}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

测试输出如下

[mrcode@study bin]$ ./file_perm.sh 
Please input a filename,I will check the filename's type and permission. 


Input a filename :ss
The filename ss does not exist
[mrcode@study bin]$ ./file_perm.sh 
Please input a filename,I will check the filename's type and permission. 


Input a filename :/etc
The filename: /etc is a directory
And the permissions for you are : readable executable
1
2
3
4
5
6
7
8
9
10
11
12
13

自己写的脚本组织来看,除了不熟悉指令用法之外,对于程序结构的抽象不够好,对比书上的,发觉这个代码组织的不错

另外,该脚本检查权限的指令是针对运行该脚本的用户所反馈的,所以当使用 root 的时候,常常会发现与 ls -l 观察到的结果并不相同

# 利用判断符号 []

除了 test 外,还可以使用中括号 [] 来判定

# 判断 ${HOME} 这个变量是否为空
[mrcode@study bin]$ [ -z "${HOME}" ]; echo $?
1
1
2
3

使用该种方式需要特别注意,因为中括号在很多地方都代表特殊符号,在 bash 的语法中作为 shell 判断时,必须要注意 中括号的两端需要有空格符来分隔

  • 在中括号内的每个组件都需要有空格来分隔
  • 在中括号内的变量,最好都以双引号括起来
  • 在中括号内的常量,都好都以单或双引号括起来

看一个例子,设置一个 name 变量,再用中括号方式判断

[mrcode@study bin]$ name="Mrcode Tset"
[mrcode@study bin]$ [ ${name} == "Mrcode" ]
-bash: [: 参数太多
# 是因为,如果 ${name} 没有使用双引号括起来就会变成  [ Mrcode Test  == "Mrcode" ]
# 中括号内的变量是以空格来分隔的,那么这里就出现了 Mrcode Test “Mrcode” 三个比较对象了
# 那么使用 [ “${name}” == "Mrcode" ] 就变成了 [ “Mrcode Test”  == "Mrcode" ]
1
2
3
4
5
6

除了以上注意之外,中括号使用方式与 test 几乎一模一样,只是中括号比较常用在 条件判断 if...then..fi 的情况中。

实践范例需求如下:

  1. 当执行一个程序的时候,要求用户选择 Y 或 N
  2. 如果用户输入 Y 或 y 时,就显示「Ok,continue」
  3. 如果用户输入 N 或 n 时,就显示「Oh,interrupt!」
  4. 如果不是以上规定字符,则显示「I don't know what your choice is」

利用中括号、&&|| 来达成

vi ans_yn.sh
#!/bin/bash
# Program:
#	This program shows the user's choice
# History:
#	2020/01/20		mrcode		first relese
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

read -p "请输入 Y/N:" yn
[ "${yn}" == "Y" -o "${yn}" == "y" ] && echo "Ok,continue" && exit 0
[ "${yn}" == "N" -o "${yn}" == "n" ] && echo "Oh,interrupt!" && exit 0
echo "I don't know what your choice is" && exit 0
1
2
3
4
5
6
7
8
9
10
11
12
13

输出测试

# 这里报错时因为 [ "${yn}" == "Y" || "${yn}" == "y" ]  中使用了 || 来达成条件判定
[mrcode@study bin]$ ./ans_yn.sh 
请输入 Y/N:n
./ans_yn.sh: 第 10 行:[: 缺少 `]'
./ans_yn.sh:行10: n: 未找到命令
./ans_yn.sh: 第 11 行:[: 缺少 `]'
./ans_yn.sh:行11: n: 未找到命令
I don't know what your choice is

# [ "${yn}" == "Y" -o "${yn}" == "y" ] 使用了 test 中的参数, -o 只要任意一个成立都算 true
# 程序正常
[mrcode@study bin]$ vim ans_yn.sh 
[mrcode@study bin]$ ./ans_yn.sh 
请输入 Y/N:n
Oh,interrupt!
[mrcode@study bin]$ ./ans_yn.sh 
请输入 Y/N:y
Ok,continue
[mrcode@study bin]$ ./ans_yn.sh 
请输入 Y/N:
I don't know what your choice is
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# shell script 的默认变量 $0,$1...

指令可以带有选项与参数,如 ls -la 可以查看包含隐藏文件的所有属性。那么 script 也可以携带参数。

# 重新启动系统的网络
[mrcode@study bin]$ file /etc/init.d/network 
/etc/init.d/network: Bourne-Again shell script, ASCII text executable
# 利用 file 指令查询该文件,显示是个可执行的 shell script 文件

# 这里携带 restart 参数,如果替换成 stop 参数就是关闭该服务了
[mrcode@study bin]$ /etc/init.d/network restart 
1
2
3
4
5
6
7

read 是使用过程中需要手动输入,而参数是可以跟随在执行命令后的,这样就比较方便

script 针对参数已经设置好一些变量名称了,对应如下

/path/to/scriptname		opt1	opt2	opt3	opt4
 		&0				 &1		 &2		 &3		 &4
1
2

除了这些数字的变量参数外,还有一些较为特殊的变量可以使用

  • $#:代表后接的参数「个数」,以上表为例这里显示「4」
  • $@:代表 「"&1" "&2" "&3" "&4"」 的意思,每个变量是独立的(用双引号括起来)
  • $*:代表「"&1c&2c&3c&4"」,其中 c 为分隔符,默认为空格,所以本例中代表「"&1 &2 &3 &4"

$@ 与 $* 基本上还是有所不同,一般使用 $@ 较多。

范例需求:输出如下数据

  • 程序的文件名
  • 共有几个参数
  • 若参数小于 2 ,则告知使用者参数数量太少
  • 全部的参数内容
  • 第一个参数
  • 第二个参数
vi prit_info.sh
#!/bin/bash
# Program:
#       输出脚本文件名,与相关参数信息
# History:
#       2020/01/20              mrcode          first relese
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo $0
echo $#
# 这样写,语法是错误的,要记得这里是使用 test 里面的语法
# 并且,不能用 ${变量} 的方式来写
[ "${$#}" < "2" ] && echo "参数数量太少,比如大于等于 2 个" && exit 0
echo $@
echo $1
echo $2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

输出

[mrcode@study bin]$ ./print_info.sh 
./print_info.sh
0
./print_info.sh:行11: 2: 没有那个文件或目录



[mrcode@study bin]$ ./print_info.sh a b
./print_info.sh
2
./print_info.sh:行11: 2: 没有那个文件或目录
a b
a
b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

以下是书上的写法

vi how_paras.sh
#!/bin/bash
# Program:
#       输出脚本文件名,与相关参数信息
# History:
#       2020/01/20              mrcode          first relese
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

echo "The script name is			==> $0"
echo "Total parameter number is		==> $#"
[ "$#" -lt 2 ] && echo "参数数量太少,比如大于等于 2 个" && exit 0
echo "Your whole parameter is		==> '$@'"
echo "The 1st parameter 			==> $1"
echo "The 2nd parameter 			==> $2"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

输出测试

[mrcode@study bin]$ ./how_paras.sh 
The script name is			==> ./how_paras.sh
Total parameter number is		==> 0
参数数量太少,比如大于等于 2[mrcode@study bin]$ ./how_paras.sh a b
The script name is			==> ./how_paras.sh
Total parameter number is		==> 2
Your whole parameter is		==> 'a b'
The 1st parameter 			==> a
The 2nd parameter 			==> b
1
2
3
4
5
6
7
8
9
10
11

# shift:造成参数变量位置偏移

先修改下上面的范例,how_paras.sh 先来看看效果什么是偏移

vi how_paras.sh
#!/bin/bash
# Program:
#       Program shows the effect of shift function
# History:
#       2020/01/20              mrcode          first relese
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

echo "Total parameter number is		==> $#"
echo -e "Your whole parameter is		==> '$@' \n"

shift	# 进行第一次 一个变量的 shift
echo "Total parameter number is		==> $#"
echo -e "Your whole parameter is		==> '$@' \n"

shift 3	# 进行第二次 三个变量的 shift
echo "Total parameter number is		==> $#"
echo "Your whole parameter is		==> '$@'"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

输出如下

[mrcode@study bin]$ ./how_paras.sh a b c d e f
Total parameter number is		==> 6		# 位偏移的参数数量,是 6 个
Your whole parameter is		==> 'a b c d e f'

Total parameter number is		==> 5		# 偏移一次后,只剩下 5 个,并且第一个参数 a 不见了
Your whole parameter is		==> 'b c d e f'

Total parameter number is		==> 2		# 第二次偏移掉 3 个后,b c d 不见了
Your whole parameter is		==> 'e f'

# 再来看看如果参数不够偏移会出现什么情况
[mrcode@study bin]$ ./how_paras.sh a b c	# 给 3 个参数
Total parameter number is		==> 3
Your whole parameter is		==> 'a b c' 

Total parameter number is		==> 2		# 第一次偏移 1 个,只生效 2 个了
Your whole parameter is		==> 'b c' 

Total parameter number is		==> 2		# 第二次偏移 3 个,发现没有生效,不够偏移
Your whole parameter is		==> 'b c'


[mrcode@study bin]$ ./how_paras.sh a b c d		# 给 4 个参数
Total parameter number is		==> 4
Your whole parameter is		==> 'a b c d' 

Total parameter number is		==> 3		# 第一次偏移 1 个,还剩下 3 个
Your whole parameter is		==> 'b c d' 

Total parameter number is		==> 0		# 第二次偏移 3 个,剩下 0 个
Your whole parameter is		==> ''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

总结如下:

  • shift 可以忽略掉 n 个参数
  • shif 中的 n 必须要有足够的参数才会生效,否则不会偏移