# 条件判断
在程序中,没有条件判断 if then
方式的话,在执行多条指令的时候,就会很麻烦。
# 利用 if...then
# 单层、简单条件判断
if [ 表达式 ]; then
当条件成立时,可以进行的指令工作内容
fi
2
3
至于表达式的编码,与上一章的 test 一致,但是有一个特别的是,可以使用 &&
与 ||
来连接多个中括号,在这里他们的含义就是表示 并且 和 或者 的意思
所以在使用中括号的时候, &&
和 ||
与指令状态下的含义不同。比如:
[ "${yn}" == "Y" -o "${yn}" == "y" ]
可以替换为下面的方式
[ "${yn}" == "Y" ] || [ "${yn}" == "y" ]
2
3
这样就很方便维护了,一个中括号一个表达式。那么将这个 script 修改为 if...then
的形式如下
[mrcode@study bin]$ 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
if [ "${yn}" == "Y" ] || [ "${yn}" == "y" ]; then
echo "Ok,continue"
exit 0
fi
# [ "${yn}" == "N" -o "${yn}" == "n" ] && echo "Oh,interrupt!" && exit 0
if [ "${yn}" == "N" ] || [ "${yn}" == "n" ]; then
echo "Oh,interrupt!"
exit 0
fi
echo "I don't know what your choice is" && exit 0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
此方式只是在代码组织上更偏向于笔者所学的 JAVA 语言了,对于变量的判定还可以使用如下的多重判断来达到效果
# 多重、复杂条件判断
简单说,上述实例对于变量 ${yn}
使用了两次 if,那么可以使用如下方式简化
if [ 条件表达式 ]; then
做点啥
else
做点啥
fi
2
3
4
5
更复杂的情况,增加 elseif ,如下
if [ 条件表达式 ]; then
做点啥
elif [ 条件表达式 ]; then
做点啥
else
做点啥
fi
2
3
4
5
6
7
改写 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
if [ "${yn}" == "Y" ] || [ "${yn}" == "y" ]; then
echo "Ok,continue"
exit 0
else
echo "Oh,interrupt!"
exit 0
fi
echo "I don't know what your choice is" && exit 0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
另一个范例知识,上一节提到参数功能($1),让用户在下达指令的时候将参数带进去,让用户输入 hello 关键词,利用参数的方法可以如下设计:
- 判断
$1
是否为 hello ,如果是,则显示「Hello, how ary you?」 - 如果无参数,则提示使用者必须要使用的参数下达方法
- 如果参数不是 hello,则提示使用者仅能使用 hello 为参数
#!/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
if [ "$1" == "hello" ]; then
echo "Hello, how ary you?"
elif [ -z "$1" ]; then
echo "请携带参数"
else
echo "只能携带参数 hello"
fi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
测试输出
[mrcode@study bin]$ ./hello-2.sh
请携带参数
[mrcode@study bin]$ ./hello-2.sh hello
Hello, how ary you?
[mrcode@study bin]$ ./hello-2.sh hellox
只能携带参数 hello
2
3
4
5
6
书上例子如下:
#!/bin/bash
# Program:
# Chek $1 is equal to "hello"
# History:
# 2020/01/20 mrcode first relese
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
if [ "$1" == "hello" ]; then
echo "Hello, how ary you?"
elif [ "$1" == "" ]; then
echo "You MUST input parameters, ex> {${0} someword}"
else
echo "The only parameter is 'hello', ex> {${0} hello}"
fi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
信息输出如下
[mrcode@study bin]$ ./hello-2.sh
You MUST input parameters, ex> {./hello-2.sh someword}
[mrcode@study bin]$ ./hello-2.sh hell
The only parameter is 'hello', ex> {./hello-2.sh hello}
[mrcode@study bin]$ ./hello-2.sh hello
Hello, how ary you?
2
3
4
5
6
笔者点评:这个显示很棒,错误提示告知了用户要怎么做
那么深入练习。
在第十章学习了 grep 指令,现在多了解一个 netstat 指令,可以查询到目前主机有开启的网络服务端口(service ports),相关功能会在 服务器架设篇 继续介绍;这里只需要知道 netstat -tuln
可以取得目前主机有启动的服务,而且取得的信息类似下面这样
[mrcode@study bin]$ netstat -tuln
Active Internet connections (only servers)
# 封包格式 本地 IP:端口 远程 IP:端口 是否监听
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:6010 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:6011 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
tcp 0 0 192.168.122.1:53 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp6 0 0 ::1:631 :::* LISTEN
tcp6 0 0 ::1:25 :::* LISTEN
tcp6 0 0 ::1:6010 :::* LISTEN
tcp6 0 0 ::1:6011 :::* LISTEN
tcp6 0 0 :::111 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
udp 0 0 0.0.0.0:48829 0.0.0.0:*
udp 0 0 192.168.122.1:53 0.0.0.0:*
udp 0 0 0.0.0.0:67 0.0.0.0:*
udp 0 0 0.0.0.0:111 0.0.0.0:*
udp 0 0 127.0.0.1:323 0.0.0.0:*
udp 0 0 0.0.0.0:672 0.0.0.0:*
udp 0 0 0.0.0.0:5353 0.0.0.0:*
udp6 0 0 :::111 :::*
udp6 0 0 ::1:323 :::*
udp6 0 0 :::672 :::*
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
重点关注 Local Address 字段(本地主机 IP 与端口对应),代表本机所启动的网络服务,127.0.0.1 则是针对本机开放,若是 0.0.0.0 或 ::: 则代表对整个 Internet 开放。每个端口 port 都有其特定的网络服务,几个常见的 port 与网络服务的关系是:
- 80:www
- 22:ssh
- 21:ftp
- 25:mail
- 111:RPC
- 631:CUPS(打印服务功能)
假设我要检测常见端口 port 21、22、25、80 时,可以通过 netstat 检测主机是否有开启这四个主要的网络服务端口,由于每个服务的关键词都是接在冒号「:」后面,所以可以截取类似「:80」来检测。那么程序如下
下面是笔者写的脚本
vim netstat.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 "现在开始检测当前主机上的服务"
echo -e "www、ftp、mail、www 服务将被检测 \n"
# 将 local Address 字段截取出来
datas=$(netstat -tuln | awk '{print $4}')
testing=$(grep ":80" ${datas})
if [ ! -z "${testing}" ]; then
echo "www"
fi
testing=$(grep ":22" ${datas})
if [ ! -z "${testing}" ]; then
echo "ssh"
fi
testing=$(grep ":21" ${datas})
if [ ! -z "${testing}" ]; then
echo "ftp"
fi
testing=$(grep ":25" ${datas})
if [ ! -z "${testing}" ]; then
echo "mail"
fi
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
不过很遗憾,grep 后只能跟一个文件路径。那么正确的做法如下
#!/bin/bash
# Program:
# Using netstat and grep to detect www⽀~Assh⽀~Aftp and mail services
# History:
# 2020/01/20 mrcode first relese
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 1. 佅~H佑~J潟¥彎¥䷾K彝¥襾A佁~Z亾@举H
echo "潎°作¨廾@妾K梾@派K弾S佉~M主彜º䷾J潚~D彜~M佊¡"
echo -e "www⽀~Aftp⽀~Amail⽀~Awww 彜~M佊¡対F被梾@派K \n"
# 2. 达[蠾L佈¤孾Z佒~L信彁¯轾S佇º
# 対F local Address 嬾W段彈ª住~V佇º彝¥L并潔~_彈~P彖~G件
testfile=/dev/shm/netstat_checking.txt
netstat -tuln | awk '{print $4}' > ${testfile}
testing=$(grep ":80" ${testfile})
if [ "${testing}" != "" ]; then
echo "www is running in you system. "
fi
testing=$(grep ":22" ${testfile})
if [ ! -z "${testing}" ]; then
echo "ssh is running in you system. "
fi
testing=$(grep ":21" ${testfile})
if [ ! -z "${testing}" ]; then
echo "ftp is running in you system. "
fi
testing=$(grep ":25" ${testfile})
if [ ! -z "${testing}" ]; then
echo "mail is running in you system. "
fi
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
32
33
34
35
输出信息如下
[mrcode@study bin]$ ./netstat.sh
现在开始检测当前主机上的服务
www、ftp、mail、www 服务将被检测
ssh is running in you system.
mail is running in you system.
2
3
4
5
6
条件判断还可以更复杂,比如:在台湾当兵是国民应尽的义务,不过,在当兵的时候总是很想退伍,那么写个脚本程序来实现:让用户输入他的退伍日期,计算出还有多少天才退伍?的功能
那么思路如下:
- 用户输入自己的退伍日期
- 由现在的日期对比退伍日期
- 由两个日期的比较来显示「还需要几天」才能够退伍的字样
温馨提示:日期可以使用 date --date="YYYYMMDD" +%s
来取得指定日期的秒数,再利用秒数相减,再计算到天
笔者从现在开始,就不再贴出自己写的代码了,先自己写,然后对照书上的,最后部分修改成书上的展示
vim cal_retired.sh
#!/bin/bash
# Program:
# You input you demobilization date,I calculate how many days before you demobilize.
# History:
# 2020/01/20 mrcode first relese
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 1. 告知用户程序的用途,并且告知应该如何输入日期格式
# 这个程序将尝试计算出,您的退伍日期还有多少天
echo "This program will try to calculate :"
echo "How many days before your demobilization date..."
read -p "Please input your demobilization date (YYYYMMDD ex>20200112):" date2
# 2. 测试判定,输入内容是否正确,使用正则表达式
date_d=$(echo ${date2} | grep '[0-9]\{8\}') # 匹配 8 位数的字符串
if [ -z "${date_d}" ]; then
# 您输入了错误的日期格式
echo "You input the wrong date format..."
exit 1
fi
# 3. 开始计算日期
declare -i date_dem=$(date --date="${date_d}" +%s) # 退伍日期秒数
declare -i date_now=$(date +%s) # 当前日期秒数
declare -i date_total_s=$((${date_dem}-${date_now})) # 剩余秒数
# 需要注意的是:这种嵌套执行的时候,括号一定要嵌套对位置
declare -i date_d=$((${date_total_s}/60/60/24)) # 转换为日
# 中括号里面不能直接使用 < 这种符号
if [ "${date_total_s}" -lt 0 ]; then
# 这里是用 -1 乘,得到是正数,标识已经退伍多少天了
echo "You had been demobilization before: $((-1*${date_d})) ago"
else
# 这里使用 总秒数 - 转换为日的变量(这里只是转换为了天),剩余数据转成小时
# 则计算到 n 天 n 小时
declare -i date_h=$(($((${date_total_s}-${date_d}*60*60*24))/60/60))
echo "You will demobilize after ${date_d} days and ${date_h} hours."
fi
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
32
33
34
35
36
37
38
39
40
41
42
测试输出
[mrcode@study bin]$ ./cal_retired.sh
This program will try to calculate :
How many days before your demobilization date...
Please input your demobilization date (YYYYMMDD ex>20200112):20200120 # 输入当天
You had been demobilization before: 0 ago
[mrcode@study bin]$ ./cal_retired.sh
This program will try to calculate :
How many days before your demobilization date...
Please input your demobilization date (YYYYMMDD ex>20200112):20200119 # 输入前一天
You had been demobilization before: 1 ago
[mrcode@study bin]$ ./cal_retired.sh
This program will try to calculate :
How many days before your demobilization date...
Please input your demobilization date (YYYYMMDD ex>20200112):20200121 # 输入明天
You will demobilize after 0 days and 8 hours.
[mrcode@study bin]$ ./cal_retired.sh
This program will try to calculate :
How many days before your demobilization date...
Please input your demobilization date (YYYYMMDD ex>20200112):2020^H^H3 # 输入错误的格式
You input the wrong date format...
[mrcode@study bin]$ ./cal_retired.sh
This program will try to calculate :
How many days before your demobilization date...
Please input your demobilization date (YYYYMMDD ex>20200112):20300120 # 输入10 年后
You will demobilize after 3652 days and 8 hours.
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
笔者总结:
- 本例结合了 grep 查找符合条件的参数,如果完全不符合,则为空白返回了
- 结合了 declare -i 定义整数变量
- 使用了
$(($(())))
嵌套指令执行语法 - 该范例还是有难度的,难点在于 用正则检查输入参数 和 计算 天 并计算小时
# 利用 case...esac
判断
作为 JAVA 程序员,这个不用多解释,直接看语法
case $变量名称 in # 关键词为 case 还有 变量前的 $ 符号
“变量内容 1”) # 每个变量内容建议用双引号括起来,关键词则为小括号
程序段
;; # 使用两个连续的分号来结尾
“变量内容 2”)
程序段
;;
*) # 最后一个变量内容需要用 * 来代表所有其他值
程序段
;;
esac # 最终的 case 结尾,就是反过来拼写的字符 esac
2
3
4
5
6
7
8
9
10
11
将上面 ./hello-2.sh
的例子使用该语法修改
./hello-3.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
case $1 in
"hello")
echo "Hello, how ary you?"
;;
"")
echo "请携带参数"
;;
*)
echo "只能携带参数 hello"
;;
esac
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
此种判定方式,针对于判定字符串时会更加的方便,CentOS6.x 以前系统很多服务的启动都是使用使用这种写法写的。虽然 CentOS7 已经使用 systemd ,不过任然有数个服务时放在 /etc/init.d
目录下的、比如有个名为 netconsole
的服务在该目录下
# 重新启动该服务
# 注意该服务需要使用 root 身份才行,一般账户可以执行,但是不会成功
/etc/init.d/netconsole restart
2
3
查阅该文件,找到文件末尾为发现以下的内容,这里就判定了输入的参数,使用的就是 case 语法
case "$1" in
stop) stop ;;
status) status ;;
start|restart|reload|force-reload) restart ;;
condrestart) condrestart ;;
*) usage ;;
esac
2
3
4
5
6
7
所以对于脚本的编写,可以参考这些已经有的,看看人家是怎么写的
一般来说,使用「case $变量 in
」语法,那个变量大致有两种取得方式:
- 直接下达:利用
script.sh variable
方式直接给 $1 变量,这也是在/etc/init.d
目录下大多数程序的设计方式 - 交互式:通过 read 指令让用户输入变量内容
下面来演示下:
- 用户输入
one、two、three
并显示在屏幕上 - 如果不是以上变量,那么提示用户只有这三种选择
vim show123.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
# 如需要让用户交互性输入,那么可以用这两行代替:case "$1" in
# read -p "请输入您的选择:" choice
# case "${choice}" in
case "$1" in
"one") echo $1 ;;
"two") echo $1 ;;
"three") echo $1 ;;
*) echo "只能输入 one、two、three" ;;
esac
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
测试输出
[mrcode@study bin]$ ./show123.sh
只能输入 one、two、three
[mrcode@study bin]$ ./show123.sh one
one
[mrcode@study bin]$ ./show123.sh two
two
[mrcode@study bin]$ ./show123.sh three
three
[mrcode@study bin]$ ./show123.sh three111
只能输入 one、two、three
2
3
4
5
6
7
8
9
10
# 利用 function 功能
函数功能,不用多说,可以被复用,优化程序结构,语法如下
function fname(){
程序段
}
2
3
TIP
由于 shell script 执行方式是由上而下,由左而右,因此 function 的代码一定要在程序的最前面
下面将 show123.sh 改写成使用 function 方式
vim show123-2.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
# 如需要让用户交互性输入,那么可以用这两行代替:case "$1" in
# read -p "请输入您的选择:" choice
# case "${choice}" in
function printit(){
echo -n "Your choice is " # -n 可以不断行连续在同一行显示
}
case "$1" in
"one") printit; echo $1 ;;
"two") printit; echo $1 | tr 'a-z' 'A-z' ;; # 转换为大写
"three") printit; echo $1 ;;
*) echo "只能输入 one、two、three" ;;
esac
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
输出信息
[mrcode@study bin]$ ./show123-2.sh one
Your choice is one
[mrcode@study bin]$ vim show123-2.sh
[mrcode@study bin]$ ./show123-2.sh tow
只能输入 one、two、three
[mrcode@study bin]$ ./show123-2.sh two
Your choice is TWO
2
3
4
5
6
7
上述代码,做了一个打印部分重复信息的功能,这个例子比较简单,当在程序中有大量重复,和大量逻辑的时候,就会体现出来了
同样,function 也可以有参数变量,改写成有参数调用函数
vim show123-3.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
function printit(){
echo "Your choice is ${1}" # 在函数域中,的参数变量,与外部的不一致
}
case "$1" in
"one") printit 1 ;;
"two") printit 2 ;;
"three") printit $1 ;;
*) echo "只能输入 one、two、three" ;;
esac
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
测试如下
[mrcode@study bin]$ ./show123-3.sh one
Your choice is 1
[mrcode@study bin]$ ./show123-3.sh two
Your choice is 2 # 可以看到,这里给定参数 1,那么在里面获取 ${1},的时候就获取到了
[mrcode@study bin]$ ./show123-3.sh three
Your choice is three # 在外部给定的是脚本中的变量 $1, 在内部也能获取到变量的具体内容
[mrcode@study bin]$ ./show123-3.sh threex
只能输入 one、two、three
2
3
4
5
6
7
8
9