# 善用判断
在 第十章 中,提到过 $? 这个变量所代表的含义,以及通过 && 和 || 来判定前一个指令执行回传值对于后一个指令是否要进行的依据。
在第十章中,判定一个目录是否存在,使用了 ll 目录 && 执行指令 的方式来判定 xx 目录是否存在,从而决定后续指令是否执行,但是有更简单的方式进行条件判断,就是通过 test 指令
# 使用 test 指令的测试功能
test 指令主要用于检测文件或相关属性时的指令和比较值,比如检查 /mrcode 是否存在时
# -e 是检测文件是否存在的选项
[root@cloud-08 script]# test -e /mrcode
 2
上面命令没有任何输出值
[root@cloud-08 script]# test -e /mrcode && echo "exist" || "not exist"
exist
# 通过与 && 或 || 可以知道是存在还是不存在了
 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 | 
| ! | 反向状态。 | 
总结完这么多的判定,就可以来写几个简单的例子。让用户输入一个文件名,我们判断:
- 该文件是否存在,若不存在则给予一个「Filename does not exist」 提示,并中断程序
 - 若该文件存在,则判断是文件还是目录:文件输出「Filename is regular file」,目录输出 「Filename is directory」
 - 判断执行者的身份对这个文件或目录所拥有的权限,并输出权限数据
 
下面是笔者写的思路,代码组织方面有点糟糕。还有指令使用不太熟悉
#!/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} 可执行"
 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 可执行
 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}"
 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
 2
3
4
5
6
7
8
9
10
11
12
13
自己写的脚本组织来看,除了不熟悉指令用法之外,对于程序结构的抽象不够好,对比书上的,发觉这个代码组织的不错
另外,该脚本检查权限的指令是针对运行该脚本的用户所反馈的,所以当使用 root 的时候,常常会发现与 ls -l 观察到的结果并不相同
# 利用判断符号 []
 除了 test 外,还可以使用中括号 [] 来判定
# 判断 ${HOME} 这个变量是否为空
[mrcode@study bin]$ [ -z "${HOME}" ]; echo $?
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" ]
 2
3
4
5
6
除了以上注意之外,中括号使用方式与 test 几乎一模一样,只是中括号比较常用在 条件判断 if...then..fi 的情况中。
实践范例需求如下:
- 当执行一个程序的时候,要求用户选择 Y 或 N
 - 如果用户输入 Y 或 y 时,就显示「Ok,continue」
 - 如果用户输入 N 或 n 时,就显示「Oh,interrupt!」
 - 如果不是以上规定字符,则显示「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
 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
 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 
 2
3
4
5
6
7
read 是使用过程中需要手动输入,而参数是可以跟随在执行命令后的,这样就比较方便
script 针对参数已经设置好一些变量名称了,对应如下
/path/to/scriptname		opt1	opt2	opt3	opt4
 		&0				 &1		 &2		 &3		 &4
 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
 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
 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"
 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
 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		==> '$@'"
 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		==> ''
 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 必须要有足够的参数才会生效,否则不会偏移