您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

Shell 流程控制

1. 流程控制概述

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

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

2. Shell 流程控制操作

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

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

if conditionthencommand1 
    command2...
    commandN 
fi

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

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

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

if conditionthencommand1 
    command2...
    commandNelsecommandfi

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

if [ $(ps -ef |grep /usr/sbin/sshd|grep -v grep|wc -l) -eq 1 ];then 
	echo "sshd server exist"elseservice sshd start && echo "启动sshd服务"fi

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

if condition1;thencommand1elif condition2;then 
    command2......elif conditionN,then
		commandNelsecommandfi

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

#! /bin/bashsys_version=$(rpm -q centos-release|cut -d- -f3)if [ $sys_version -eq 6 ];thenecho "sysversion is $sys_version"elif [ $sys_version -eq 7 ];thenecho "sysversion is $sys_version"elseecho "sysversion is ${sys_version}"fi

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

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

for var in item1 item2 ... itemNdocommand1
    command2...
    commandNdone

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

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

SUM=0for num in $(seq 1 10)dolet SUM=${SUM}+${num}doneecho "1-10的和为:${SUM}"

当然在 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--cvm-0 ~]# bash 1.shroot
x
0
0
root
/root
/bin/bash[root@xuel--cvm-0 ~]# cat /etc/passwd |head -1root:x:0:0:root:/root:/bin/bash

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

for (( ; ; ))

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

while 条件表达式:docommanddone

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

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

#!/bin/bashN=0while [ $N -lt 5 ]; do
  let N++  echo $Ndone

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

#!/bin/bash#function:check urlfilename=urllist.txtfor url in $(cat $filename)do
  status=`curl -I -- 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

编写 urllist.txt

[root@xuel--cvm ~]# cat urllist.txtbaidu.com.[root@xuel--cvm ~]# bash urlcheck.shUrl:baidu.com is ok!   status is Url:. is error! status is

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

while :docommanddone或者while truedocommanddone

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

until 条件表达式docommanddone

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

NUM=0until [ ${NUM} -ge 5 ]do 
	let NUM++	echo $NUMdone

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

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

break

#!/bin/bashN=0while true; dolet N++if [ $N -eq 5 ]; thenbreakfiecho $Ndone

continue

#!/bin/bashN=0while [ $N -lt 5 ]; dolet N++if [ $N -eq 3 ]; thencontinuefiecho $Ndone

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

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

case 模式名	in模式 1)命令;;模式 2)命令;;*)不符合以上模式执行的命令
esac

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

#!/bin/bashcase $1 instart)echo "start."   ;;stop)echo "stop.";;restart)echo "restart.";;*)echo "Usage: $0 {start|stop|restart}"esac

3. 实例

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

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

具体实现 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_DIRdofind $file -type f -exec $SCAN_CMD \{\} \;>> $SCAN_FILE_BIN 2>/dev/null	doneecho "扫描完成,可利用以下命令后期对进行校验"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

测试

[root@xuel--cvm ~]# bash file_scan.sh###########################################                                        ##        利用md5sum对进行校验        ##                                        ###########################################: 全盘扫描
: bin path扫描
: EXITPlease input your choice:
正在扫描/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin可执行,请稍等,路径:/tmp/scan_2020-271703_bin.txt
扫描完成,可利用以下命令后期对进行校验/usr/bin/md5sum - /tmp/scan_2020-271703_bin.txt |grep -v 'OK$'[root@xuel--cvm ~]# /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/lvd: 确定/sbin/e2undo: 确定...

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

[root@xuel--cvm ~]# bash file_scan.sh###########################################                                        ##        利用md5sum对进行校验           ##                                        ###########################################: 全盘扫描
: bin path扫描
: EXITPlease input your choice:
you choce channel!

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

[root@xuel--cvm ~]# bash file_scan.sh###########################################                                        ##        利用md5sum对进行校验        	 ##                                        ###########################################: 全盘扫描
: bin path扫描
: EXITPlease input your choice:
正在全盘扫描,请稍等!路径:
扫描完成,可利用以下命令后期对进行校验/usr/bin/md5sum - /tmp/scan_2020-271703_fall.txt |grep -v 'OK$'[root@xuel--cvm ~]# /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/uevent: 确定/sys/devices/platform/platform-framebuffer/modalias: 确定/sys/devices/platform/platform-framebuffer/power/control: 确定/sys/devices/platform/platform-framebuffer/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

4. 注意事项

对于条件语句中的多分支 if 语句,结尾肯定是有 else 来完成,需要注意此点;

在编写流程控制时候,可以根据自己的习惯对于 command 中的操作进行缩进,可以为几个空格,也可以为 tab 键,建议使用制表符;

流程控制语句可以相互嵌套,例如 for 循环中可以有 if ,if 判断中也可以 for 循环等,根据需求具体灵活运用;

case 语句与 if 多分支语句一样,不同点在与 case 语句只能判断一种条件关系,而 if 多分支可以对多种条件进行判断。

5. 小结

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


联系我
置顶