全国协议5人面授小班,企业级独立开发考核,转业者的IT软件工程师基地 登录/注册 | 如何报名
当前位置: 服务端相关   >  Shell 流程控制
admin · 更新于 2021-08-06

1. 流程控制概述

1.1 流程控制简介

Shell 脚本默认从上到下顺序执行,在程序运行中,会遇到很多种情况,对应不同情况执行对应的操作,例如对于一批数据需要进行执行重复工作,这些都需要我们使用特定的流程控制语句来实现,我们想要程序完成预定的操作,就需要熟练掌握流程控制语句,不同的流程控制语句有不同的适应场景。

1.2 为什么要用流程控制

流程控制是每种编程语言控制逻辑走向和执行次序的重要组成部分,流程控制可以说是一门语言的 “经脉”,其控制着程序的运行走向,所以熟练掌握流程控制语句才能更好的控制整个脚本的运行结果,来完成我们的需求。

2. Shell 流程控制操作

2.1 条件语句

顾名思义,就是满足特定条件执行对应操作,按照顺序从上到下,条件语句 if 通常需要与 test 命令配合使用,当满足条件则执行 then 后的 command,否则继续往下运行执行对应的 command,条件语句 if 是 Shell 编程中最基础的条件判断流程语句。

2.1.1 单分支 if 语句

顾名思义就是只有一个 if 语句块包含的语句,condition 为正确则执行 then 内的命令,语法:

if conditionthen
    command1 
    command2    ...
    commandN 
fi
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

对于 sshd 进程是否存在,可以使用单分支 if 语句来判断,例如:

if [ $(ps -ef |grep /usr/sbin/sshd|grep -v grep|wc -l) -eq 1 ];then 
	echo "sshd server exist"fi
代码块
  • 1
  • 2
  • 3

2.1.2 双分支 if 语句

多分支 if 语句存在 else 情况,语法:

if conditionthen
    command1 
    command2    ...
    commandNelse
    commandfi
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

统一判断 sshd 服务,可以在 else 中进行一些列操作,例如:

if [ $(ps -ef |grep /usr/sbin/sshd|grep -v grep|wc -l) -eq 1 ];then 
	echo "sshd server exist"else
	service sshd start && echo "启动sshd服务"fi
代码块
  • 1
  • 2
  • 3
  • 4
  • 5

2.1.3 多分支 if 语句

顾名思义有多个 if 条件,在此利用 elif 来表示,注意最后有一个 else 结尾。

if condition1;then
    command1elif condition2;then 
    command2    ......elif conditionN,then
		commandNelse
    commandfi
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们的 linux 系统有多个版本,可以利用多分支 if 语句来进行判断,例如:

#! /bin/bashsys_version=$(rpm -q centos-release|cut -d- -f3)if [ $sys_version -eq 6 ];then
	echo "sysversion is $sys_version"elif [ $sys_version -eq 7 ];then
	echo "sysversion is $sys_version"else
	echo "sysversion is ${sys_version}"fi
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.2 循环语句

对于一批数据,我们需要对其重复进行操作的时候,就需要利用循环语句来操作。

2.2.1 for 循环

for 循环语句通常应用在可预估数量的一批对象操作中,默认 for 循环的取值列表是以 $IFS 分割,默认 $IFS 为空白符,如果我们有其他需求可以更改,语法为:

for var in item1 item2 ... itemNdo
    command1
    command2    ...
    commandNdone
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

通过 for 循环每次遍历一个后面跟的对象,在 do…done 操作块中对对象进行一些列操作。

例如我们来求和 1-10 的和:

SUM=0for num in $(seq 1 10)do
		let SUM=${SUM}+${num}doneecho "1-10的和为:${SUM}"
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

当然在 for 循环语句里面也可以配合 if 条件判断或其他流程控制语句进行操作。

在此我们举例修改 $IFS 的应用场景,首选备份默认当前的 $IFS,之后为其赋值新的 $IFS 为:,在对 /etc/passwd 进行操作完成后,恢复之前的 $IFS, 在此我们就利用改变 $IFS 对 /etc/passwd 的单个字段进行了变量操作。

#!/bin/bashOLD_IFS=$IFSIFS=":"for i in $(head -1 /etc/passwd); doecho $idoneIFS=${OLD_IFS}[root@xuel-terraform-cvm-0 ~]# bash 1.shroot
x
0
0
root
/root
/bin/bash[root@xuel-terraform-cvm-0 ~]# cat /etc/passwd |head -1root:x:0:0:root:/root:/bin/bash
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

for 循环如果条件永远满足则,一直执行内部的命令。

for (( ; ; ))
代码块
  • 1

2.2.2 while 循环

while 循环同样为循环,与 for 循环功能一样,利用 for 循环的语句同样也可以使用 while 循环完成,但是 while 循环通常用于处理未知数量对象的操作,语法:

while 条件表达式:do    commanddone
代码块
  • 1
  • 2
  • 3

while 通常与 test 语句配合使用,如果条件表达式成立,则一直执行。

例如求和打印 1-5 个数:

#!/bin/bashN=0while [ $N -lt 5 ]; do
  let N++  echo $Ndone
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

也可以利用 read 读入文件,例如我们来读入一个写有 ip 或域名列表的文件,来判断该文件内的域名或 IP 网络是否可达。

#!/bin/bash#function:check urlfilename=urllist.txtfor url in $(cat $filename)do
  status=`curl -I --connect-timeout 5 $url -s|awk '/HTTP/{print $2}'`  if [[ $status == "200" ]];then
          echo "Url:$url is ok!   status is $status"
  else
          echo "Url:$url is error! status is $status"
  fidone
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

编写 urllist.txt

[root@xuel-terraform-cvm-0 ~]# cat urllist.txtbaidu.com114.114.114.114[root@xuel-terraform-cvm-0 ~]# bash urlcheck.shUrl:baidu.com is ok!   status is 200Url:114.114.114.114 is error! status is
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果 while 的判断条件为永远为 true,则称为无限循环,会一直执行内部的操作,例如:

while :do
    commanddone或者while truedo
    commanddone
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.2.3 until 循环

until 循环与 while 循环刚好相反,其也有一定的应用场景,其为条件表达式为 true 时停止,否则一直运行,语法:

until 条件表达式do
    commanddone
代码块
  • 1
  • 2
  • 3
  • 4

例如我们使用 until 来打印 1-5 数字:

NUM=0until [ ${NUM} -ge 5 ]do 
	let NUM++	echo $NUMdone
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.2.4 break 与 continue

与上面三个循环语句不同的是,break 为跳出循环,continue 则为不执行下一次操作,直接跳到下一次循环。

我们可以利用 break 来跳出终止循环。

  • break
#!/bin/bashN=0while true; do
    let N++    if [ $N -eq 5 ]; then
    break
    fi
    echo $Ndone
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • continue
#!/bin/bashN=0while [ $N -lt 5 ]; do
    let N++    if [ $N -eq 3 ]; then
        continue
    fi
    echo $Ndone
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

利用 continue 来跳过特定的条件操作。

2.3 选择语句

2.3.1 case 语句

选择语句 case 可以在特定的几个条件中选择某一个进行执行,其他 case 可以利用 if 多分支来替代。

case 模式名	in
    模式 1)
        命令        ;;
    模式 2)
        命令        ;;
    *)
        不符合以上模式执行的命令
esac
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

例如我们服务的启动操作脚本就是利用 case 语句来,当用户的输入与模式名相匹配则执行对应的命令。

#!/bin/bashcase $1 in
    start)
        echo "start."   
        ;;
    stop)
        echo "stop."
        ;;
    restart)
        echo "restart."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"esac
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3. 实例

3.1 需求

编写一个脚本,来对当前系统 PATH 目录下的二进制执行文件进行,或者对指定全盘进行 md5 扫描,后期可以配合定时任务来监控文件是否变化,用于文件权限及内容的管控。

3.2 思路

文件扫描可以使用 md5sum 工具执行,对目录遍历需要使用 for 循环,现在执行方式可以使用 case 来操作,其中一些操作会涉及到 sed 的命令,后期我们会针对这些命令进行单独章节详细讲解,在本需求案例中着重思考 Shell 中流程控制的作用。

3.3 实现

  • 具体实现 shell 脚本
#!/bin/bash# Description: count file scripts# Auth: kaliarch# Email: kaliarch@163.com# function: count file# Date: 2020-03-28 14:00# Version: 1.0# 定义文件扫描目录SCAN_DIR=`echo $PATH |sed 's/:/ /g'`SCAN_CMD=`which md5sum`
SCAN_FILE_FAIL="/tmp/scan_$(date +%F%H%m)_fall.txt"SCAN_FILE_BIN="/tmp/scan_$(date +%F%H%m)_bin.txt"scan_fall_disk() {
	echo "正在全盘扫描,请稍等!文件路径:$SCAN_FILE_FALL"
	find / -type f ! -path "/proc/*" -exec $SCAN_CMD \{\} \;>> $SCAN_FILE_FAIL 2>/dev/null	echo "扫描完成,可利用以下命令后期对文件进行校验"
	echo "$SCAN_CMD -c $SCAN_FILE_FAIL |grep -v 'OK$'"}scan_bin() {
	echo "正在扫描$PATH可执行文件,请稍等,文件路径:$SCAN_FILE_BIN"
	for file in $SCAN_DIR
	do
		find $file -type f -exec $SCAN_CMD \{\} \;>> $SCAN_FILE_BIN 2>/dev/null	done
	echo "扫描完成,可利用以下命令后期对文件进行校验"
	echo "$SCAN_CMD -c $SCAN_FILE_BIN |grep -v 'OK$'"}clearecho "##########################################"echo "#                                        #"echo "#        利用md5sum对文件进行校验        #"echo "#                                        #"echo "##########################################"echo "1: 全盘扫描"echo "2: bin path扫描"echo "3: EXIT"# 选择扫描方式read -p "Please input your choice:" methodcase $method in 1)
	scan_fall_disk;;2)
	scan_bin;;3)
        echo "you choce channel!" && exit 1;;*)
	echo "input Error! Place input{1|2|3}" && exit 0;;esac
代码块
  • 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
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 测试
[root@xuel-terraform-cvm-0 ~]# bash file_scan.sh###########################################                                        ##        利用md5sum对文件进行校验        ##                                        ###########################################1: 全盘扫描2: bin path扫描3: EXITPlease input your choice:2正在扫描/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin可执行文件,请稍等,文件路径:/tmp/scan_2020-03-271703_bin.txt
扫描完成,可利用以下命令后期对文件进行校验/usr/bin/md5sum -c /tmp/scan_2020-03-271703_bin.txt |grep -v 'OK$'[root@xuel-terraform-cvm-0 ~]# /usr/bin/md5sum -c /tmp/scan_2020-03-271703_bin.txt |grep -v 'OK$'/sbin/mii-tool: 确定/sbin/wipefs: 确定/sbin/blkdiscard: 确定/sbin/rtmon: 确定/sbin/shutdown: 确定/sbin/aureport: 确定/sbin/plymouthd: 确定/sbin/udevd: 确定/sbin/lvmetad: 确定/sbin/e2undo: 确定...
代码块
  • 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

当我们输入数字 2 的时候即对 bin 路径执行扫描,扫描完成后会生成对应的扫描文件,可以执行 /usr/bin/md5sum -c /tmp/scan_2020-03-271703_bin.txt |grep -v 'OK$' 来进行后期文件校验。

[root@xuel-terraform-cvm-0 ~]# bash file_scan.sh###########################################                                        ##        利用md5sum对文件进行校验           ##                                        ###########################################1: 全盘扫描2: bin path扫描3: EXITPlease input your choice:3you choce channel!
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

当输入 3 程序退出,在此就是使用 case 来完成。

[root@xuel-terraform-cvm-0 ~]# bash file_scan.sh###########################################                                        ##        利用md5sum对文件进行校验        	 ##                                        ###########################################1: 全盘扫描2: bin path扫描3: EXITPlease input your choice:1正在全盘扫描,请稍等!文件路径:
扫描完成,可利用以下命令后期对文件进行校验/usr/bin/md5sum -c /tmp/scan_2020-03-271703_fall.txt |grep -v 'OK$'[root@xuel-terraform-cvm-0 ~]# /usr/bin/md5sum -c /tmp/scan_2020-03-271703_fall.txt |grep -v 'OK$' |more/sys/devices/platform/uevent: 确定/sys/devices/platform/power/control: 确定/sys/devices/platform/power/wakeup: 确定/sys/devices/platform/pcspkr/uevent: 确定/sys/devices/platform/pcspkr/modalias: 确定/sys/devices/platform/pcspkr/power/control: 确定/sys/devices/platform/pcspkr/power/wakeup: 确定/sys/devices/platform/platform-framebuffer.0/uevent: 确定/sys/devices/platform/platform-framebuffer.0/modalias: 确定/sys/devices/platform/platform-framebuffer.0/power/control: 确定/sys/devices/platform/platform-framebuffer.0/power/wakeup: 确定/sys/devices/platform/serial8250/uevent: 确定/sys/devices/platform/serial8250/modalias: 确定/sys/devices/platform/serial8250/power/control: 确定/sys/devices/platform/serial8250/power/wakeup: 确定/sys/devices/platform/serial8250/tty/ttyS1/uevent: 确定/sys/devices/platform/serial8250/tty/ttyS1/dev: 确定/sys/devices/platform/serial8250/tty/ttyS1/power/cont
代码块
  • 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
  • 32

4. 注意事项

  • 对于条件语句中的多分支 if 语句,结尾肯定是有一个 else 来完成,需要注意此点;
  • 在编写流程控制时候,可以根据自己的习惯对于 command 中的操作进行缩进,可以为几个空格,也可以为一个 tab 键,建议使用一个制表符;
  • 流程控制语句可以相互嵌套,例如 for 循环中可以有 if 条件判断,if 添加判断中也可以添加 for 循环等,根据需求具体灵活运用;
  • case 语句与 if 多分支语句一样,不同点在与 case 语句只能判断一种条件关系,而 if 多分支可以对多种条件进行判断。

5. 小结

流程控制语句是我们 Shell 编写的经络,利用它来控制程序的走向,需要我们能熟练掌握最常用的流程控制语句,并知道其对应的适应场景,灵活配合使用。


为什么选择汉码未来