# 善用判断
在 第十章 中,提到过 $?
这个变量所代表的含义,以及通过 && 和 ||
来判定前一个指令执行回传值对于后一个指令是否要进行的依据。
在第十章中,判定一个目录是否存在,使用了 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 必须要有足够的参数才会生效,否则不会偏移