Linux 入门到精通,真的不用背命令!零基础小白靠「场景化学习法」,3 个月拿下运维 offer,第二十七天
shell脚本编程
六、流程控制
条件选择
选择执行if语句
if结构:
[root@ansible-salve1 shell]# help if
if: if 条件; then 命令; [ elif 命令; then 命令; ]... [ else 命令; ] fi根据条件执行命令。`if COMMANDS'列表被执行。如果退出状态为零,则执行`then COMMANDS' 列表。否则按顺序执行每个 `elif COMMANDS'列表,并且如果它的退出状态为零,则执行对应的 `then COMMANDS' 列表并且 if 命令终止。否则如果存在的情况下,执行 `else COMMANDS'列表。整个结构的退出状态是最后一个执行的命令的状态,或者如果没有条件测试为真的话,为零。退出状态:返回最后一个执行的命令的状态。
[root@ansible-salve1 shell]#
单分支
if [ 条件判断式 ];then命令
fi
或者
if [ 条件判断式 ]then命令
fi
双分支
if [ 条件判断式 ]then命令
else命令
fi
多分支
if [ 条件判断式1 ]then命令
elif [ 条件判断式2 ]then 命令
...
...
else命令
fi
示例:依据BMI参数写出判断语句
[root@ansible-salve1 shell]# vim info5.sh
#!/bin/bash
read -p "请输入身高(m为单位):" HIGH
if [[ ! "$HIGH" =~ ^[0-2].?[0-9]{,2}$ ]]thenecho "请不要输入错误的身高";exit 1;
fi
read -p "请输入体重(Kg为单位):" WEIGHT
if [[ ! "$WEIGHT" =~ ^[0-9]{1,3}$ ]]thenecho "请不要输入错误的体重";exit 1;
fi
BMI=`echo $WEIGHT/$HIGH^2|bc`
if [ $BMI -le 18 ] ;thenecho "你太瘦了,请注意身体建康"
elif [ $BMI -lt 24 ] ;thenecho "身材很棒!"
elseecho "你太胖了,注意节食,加强运动"
fi
[root@ansible-salve1 shell]# chmod +x info5.sh
[root@ansible-salve1 shell]# ./info5.sh
说明:
在面对多个if条件时,程序会依次对每个条件进行判断。一旦遇到第一个符合条件的“真”条件,就会执行相应的分支操作,并就此结束整个if语句。此外,if语句可以相互嵌套,从而实现更复杂的逻辑控制。
条件判断case语句
格式
[root@ansible-salve1 shell]# help case case: case 词 in [模式 [| 模式]...) 命令 ;;]... esac基于模式匹配来执行命令。基于 PATTERN 模式匹配的词 WORD,有选择的执行 COMMANDS 命令。`|' 用于分隔多个模式。退出状态:返回最后一个执行的命令的状态。 [root@ansible-salve1 shell]# case 变量引用 in PAT1)分支1;; PAT2)分支2;; ... *)默认分支;; esac
case支持glob风格的通配符
*: 任意长度任意字符
?: 任意单个字符
[]: 指定范围内的任意单个字符
|: 或,如a|b ,a或b
示例:
[root@ansible-salve1 shell]# vim info6.sh
#!/bin/bash
read -p "Do you agree(yes/no)?" INPUT
INPUT=`echo $INPUT | tr 'A-Z' 'a-z'`
case $INPUT in
y|yes) echo "You input is Yes";;
n|no)echo "You input is No";;
*) echo "Input fales,please input yes or no!"
esac
[root@ansible-salve1 shell]# chmod +x info6.sh
[root@ansible-salve1 shell]# ./info6.sh
Do you agree(yes/no)?yes
You input is Yes
[root@ansible-salve1 shell]# ./info6.sh
Do you agree(yes/no)?no
You input is No
[root@ansible-salve1 shell]# ./info6.sh
Do you agree(yes/no)?111
Input fales,please input yes or no!
[root@ansible-salve1 shell]#
工作选项
[root@ansible-salve1 shell]# vim info7.sh
#!/bin/bash
cat <<EOF
请选择:
1.备份文件
2.清理日志文件
3.软件升级
4.软件回滚
5.删库跑路
EOF
read -p "请输入上面的数字1-5:" MENU
case $MENU in
1)./backup.sh;;
2)echo "清理日志";;
3)echo "软件升级";;
4)echo "软件回滚";;
5)echo "删库跑路";;
*)echo "Input False"
esac
[root@ansible-salve1 shell]# chmod +x info7.sh
[root@ansible-salve1 shell]# ./info7.sh
请选择:
1.备份数据库
2.清理日志文件
3.软件升级
4.软件回滚
5.删库跑路
请输入上面的数字1-5:1
备份成功
[root@ansible-salve1 shell]#
循环
循环执行介绍
将特定的代码段重复执行多次通常涉及到循环结构,该结构包含进入循环的条件以及退出循环的条件。在某些情况下,循环需要执行的次数是预先知道的。然而,在其他情况下,循环的次数可能事先无法确定,这取决于程序运行时的具体条件。
常见的循环的命令:
for,while
for循环
格式1:
# 第一种写法
for NAME [in words ...]; do commands;done
# 第二种写法
for 变量 in 列表循环体
done
# 第三种写法
for 变量 in 列表
do循环体
done
执行机制:
依次将列表中的元素赋值给”变量名“;每次赋值后即执行一次循环体;直到列表中的元素耗尽,循环结束
示例:
[root@ansible-salve1 shell]# for i in 1 2 3 4 5 6 ; do echo i=$i;done
i=1
i=2
i=3
i=4
i=5
i=6
[root@ansible-salve1 shell]# for i in {1..10..2};do echo i=$i;done
i=1
i=3
i=5
i=7
i=9
[root@ansible-salve1 shell]# for i in `seq 10`;do echo i=$i;done
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
i=10
[root@ansible-salve1 shell]# sum=0; for i in `seq 10`;do let sum+=i;done;echo sum=$sum
sum=55
[root@ansible-salve1 shell]#
for循环列表生成方式
直接给出列表
整数列表
{start..end}
$(seq [start [step]] end)
返回列表的命令
$(COMMAND)
使用glob,如:*.sh
# 示例
[root@ansible-salve1 log]# for FILE in `ls /var/log/*.log`;do ll $FILE;done
-rw-------. 1 root root 0 11月 16 08:15 /var/log/boot.log
-rw------- 1 root root 675624 11月 16 07:48 /var/log/dnf.librepo.log
-rw------- 1 root root 81415 11月 16 07:48 /var/log/dnf.log
-rw------- 1 root root 1208 11月 16 07:38 /var/log/dnf.rpm.log
-rw------- 1 root root 39189 11月 16 07:48 /var/log/hawkey.log
-rw------- 1 root root 775 11月 15 04:46 /var/log/vmware-network.1.log
-rw------- 1 root root 775 11月 15 03:54 /var/log/vmware-network.2.log
-rw------- 1 root root 775 11月 14 21:22 /var/log/vmware-network.3.log
-rw------- 1 root root 775 11月 14 06:54 /var/log/vmware-network.4.log
-rw------- 1 root root 775 11月 14 01:21 /var/log/vmware-network.5.log
-rw------- 1 root root 775 11月 13 21:04 /var/log/vmware-network.6.log
-rw------- 1 root root 775 11月 13 04:59 /var/log/vmware-network.7.log
-rw------- 1 root root 775 11月 12 22:00 /var/log/vmware-network.8.log
-rw------- 1 root root 775 10月 28 21:42 /var/log/vmware-network.9.log
-rw------- 1 root root 775 11月 16 07:28 /var/log/vmware-network.log
-rw-r--r--. 1 root root 80892 11月 16 07:28 /var/log/vmware-vmsvc.log
[root@ansible-salve1 log]#
变量引用,如: $@, $#,$* 位置参数
# 示例
[root@ansible-salve1 shell]# vim info8.sh
#!/bin/bash
sum=0;
for i in $@;dolet sum+=idoneecho sum=$sum
[root@ansible-salve1 shell]# chmod +x info8.sh
[root@ansible-salve1 shell]# ./info8.sh 1 2 4 5 6 7 9
sum=34
[root@ansible-salve1 shell]#
示例:打印99乘法表
[root@ansible-salve1 shell]# vim info9.sh
#!/bin/bash
for i in {1..9};dofor j in `seq $i`;doecho -e "${j}x$i=$((j*i))\t\c "doneecho
done
[root@ansible-salve1 shell]# chmod +x info9.sh
[root@ansible-salve1 shell]# ./info9.sh
1x1=1
1x2=2 2x2=4
1x3=3 2x3=6 3x3=9
1x4=4 2x4=8 3x4=12 4x4=16
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
[root@ansible-salve1 shell]#
生产案例:
将指定目录下的所有文件的后缀都进行备份
# 取出文件的后缀名
[root@ansible-salve1 shell]# echo nianling.sh | sed -En 's/^(.*)\.([^.]+)$/\2/p'
sh
# 取出文件的前缀名
[root@ansible-salve1 shell]# echo nianling.sh | sed -En 's/^(.*)\.([^.]+)$/\2/p'
nainling
[root@ansible-salve1 shell]# vim info10.sh
#!/bin/bash
DIR=/root/shell
cd $DIR
for FILE in *;doPRE=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\1/p'`cp $FILE $PRE.bak
done
[root@ansible-salve1 shell]# chmod +x info10.sh
面试题:
要求将目录中YYYY-MM-DD中的所有文件移动到YYYY-MM/DD目录下
1.创建YYYY-MM-DD,当前日期一年前365天到目前一共365个目录,里面有10个文件,$random.log[root@ansible-salve1 ~]# vim mkdir.sh
#!/bin/bash
for i in {1..365};doDIR=`date -d "-$i day" +%F`mkdir /data/test/$DIRcd /data/test/$DIRfor n in {1..10};dotouch $RANDOM.logdone
done
# 2.移动到YYYY-MM/DD下[root@ansible-salve1 ~]# mkdir mv.sh
#!/bin/bash
DIR=/data/test
cd $DIR
for DIR in *;doYYYY_MM=`echo $DIR | cut -d"_" -f1,2`DD=`echo $DIR |cut -d"_" -f3`[ -d $YYYY_MM/$DD ] || mkdir -p $YYYY_MM/$DD &> /dev/nullmv $DIR/* $YYYY_MM/$DD
done
面试题:
扫描一个网段192.168.80.0/24,判断输入的网段中主机的在线状态,将在线的主机IP打印出来
[root@ansible-salve1 ~]# vim ip.sh
#!/bin/bash
IP=192.168.80.
for i in `seq 254`;do{ping -c1 -W1 $IP$i &> /dev/null && echo $IP$i "is up"}&
done
wait
格式2:
双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使用bash shell 实现C语言风格的变量操作l=10;((l++))
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do循环体
done
说明:
控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束后会先进行控制变量修正运算,然后在做条件判断
示例:1到100的和
#!/bin/bash
sum=0
for i in {1..100} ;dolet sum+=i
done
echo sum=$sum
sum=0
for ((i=1;i<=100;i++));dolet sum+=i
done
echo sum=$sum
示例:九九乘法表
#!/bin/bash
for ((i=1;i<10;i++));dofor ((j=1;j<=i;j++));doecho -e "${j}x$i=$((j*i))\t\c"doneecho
done
示例:打印三角形
# 直角三角形
#!/bin/bash
for((i=1;i<=10;i++));dofor((j=1;j<=2*i-1;j++));doecho -e '*\c'doneecho
done
# 等腰三角形
#!/bin/bash
for((i=1;i<=10;i++));dofor((k=1;k<=10-i;k++));doecho -e ' \c'donefor((j=1;j<=2*i-1;j++));doecho -e '*\c'doneecho
done
while循环
[root@ansible-salve1 shell]# help while
while: while 命令; do 命令; done只要测试成功即执行命令。只要在 `while' COMMANDS 中的最终命令返回结果为0,则展开并执行 COMMANDS 命令。退出状态:返回最后一个执行的命令的状态。
[root@ansible-salve1 shell]#
格式:
while command; do commands;done
while condition;do 循环体 done
说明:
在循环控制结构中,条件判断扮演着至关重要的角色。循环开始前,系统会首先对条件进行初次评估。在每一次循环迭代结束后,条件会再次被判断。如果条件为真,则循环体将执行一次。如此往复,直到条件评估为假,循环才终止。因此,循环控制条件通常涉及一个循环控制变量,这个变量的值在循环过程中不断被更新,以确保循环能够在满足特定条件时停止。
进入条件:condition为True
退出条件:condition为False
无限循环
while true;do循环体
done
案例1:猜数游戏
#!/bin/bash
# 脚本生成一个100以内得随机数,提示用户猜数字,根据用户得输入,提示用户猜小了或猜大了,直至用户猜对脚本结束
# RANDOM为系统自带的系统变量,值为0~32767的随机数
# 使用取余算法将随机数变为1~100的随机数
num=$[RANDOM%100+1]
echo $num
# 使用read 提示用户猜数字
# 使用if判断用户猜数字的大小关系:
# -eq(等于),-ne(不等于),-gt(大于),-ge(大于等于),-lt(小于),-le(小于等于)
while :
do read -p "数字炸弹在1~100的随机数,请选择数字:" nif [ $n -eq $num ];thenecho "恭喜,爆炸了"exitelif [ $n -gt $num ];thenecho "你猜大了,缩小范围为:0~$n之中"elseecho "你猜小了,缩小范围为$n~100之中"fi
done
案例2:DNS地址查询小程序
#!/bin/bash
# Function 基于DNS字典文件做一个中国电信DNS查询地址
IPPattern='^(\<([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\>\.){3}\<([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\>$'
echo -e "\e[34m --------------------------这是一个指定地区查询对应的DNS地址小程序-------------------------- \e[0m"
read -p "请输入你想查询的省份:" Province
read -p "请输入你想查询的城市: " City
echo -e "\e[34m --------------------------你查询的地址为$Province$City ,正在为你查询,请稍后---------------------- \e[0m"
count=0
while read line
doprovince=$(echo $line | awk -F " " '{print $2}') #省份city=$(echo $line | awk -F " " '{print $3}') #城市if [[ "${city}" =~ ${IPPattern} ]];thencity=" "dns_address=$(echo $line | awk -F " " '{print $3}') #DNS地址elsedns_address=$(echo $line | awk -F " " '{print $4}') #DNS地址fiif [[ "$province" == $Province* ]];thenif [[ "$city" == $City* ]];thenecho -e "\e[32m $province $city地址为:$dns_address \e[0m"fifi
done < /root/re_study/Shell_Study/dns_list.txt
echo -e "\e[34m --------------------------这就是以上对$Province$City的DNS地址查询,查询结果仅供参考---------------------- \e[0m"
exit 0
案例3:持续检查服务器负载
#!/bin/bash
#Fuction:持续检查服务器负载,如果负载过高则发送警告邮件
while true
doload=$(uptime | awk '{print $10}'|tr -s "," ' ')if [ $(echo "$load > 1.0" | bc) -eq 1 ];thenecho "Warning:Server load is high:$load" | mail -s "Server load warning" admin@example.comfisleep 300
done
# echo "$load > 1.0" | bc 返回一个数字布尔值,0为True,1为False
案例4:持续检查应用日志
#!/bin/bash
#fuction:持续检查错误日志.如有错误日志则发送警告邮件
while true
doif grep -q "Error" /var/log/maillog;thenecho "Warning:Error log found in maillog" | mail -s "Application error" admin@example.comfisleep 600
done
案例5:持续同步文件夹
#!/bin/bash
#Fuction:持续将本地文件夹同步到远程服务器上
while true
dorsync -avz /local/folder/ user@remote-server:/remote/folder/sleep 3600
done
案例6:持续备份数据库
#!/bin/bash
#Fuction:持续备份数据库,每天备份一次
while true:
donow=$(date +"%Y-%m-%d-%H-%M-%S")mysqldump -uroot -p password mydb > "/backup/mydb_$now.sql"sleep 86400
done
案例7:逐行读取文件
#!/bin/bash
#Fuction:逐行读取文件
while read line
doecho $line
done < filename.txt
案例8:计数器循环
#!/bin/bash
#Fuction:计数器循环器
counter=0
while [ $counter -lt 10 ]
doecho $countercounter=$((counter+1))
done
案例9:监听文件变化
#!/bin/bash
while inotifywait -e modify filename
doecho "File Changed"sleep 10
done
循环控制语句continue
continue[N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
格式:
for (());do循环体1...if command2;thencontinuefiCMDn....
done
示例: 结束最内层循环
#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && continueecho $jdoneecho ----------------------------done
示例:结束外层循环
#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && continue 2echo $j doneecho ----------------------------done
[root@ansible-salve1 shell]# vim info14.sh
[root@ansible-salve1 shell]# bash info14.sh
0
1
2
3
4
0
1
2
3
4
循环控制语句break
break[N]:提前结束第N层后的全部循环;最内层为第1层,默认为1
示例:结束内层循环
#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && breakecho $j doneecho ----------------------------done
[root@ansible-salve1 shell]# vim info14.sh
[root@ansible-salve1 shell]# bash info14.sh
0
1
2
3
4
----------------------------
0
1
2
3
4
----------------------------
示例:结束外层循环
#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && break 2echo $j doneecho ----------------------------done
[root@ansible-salve1 shell]# vim info14.sh
[root@ansible-salve1 shell]# bash info14.sh
0
1
2
3
4
[root@ansible-salve1 shell]#
案例1:餐厅简易点餐系统
#!/bin/bash
sum=0
COLOR='echo -e \033[1;31m'
COLOR2='echo -e \033[1;32m'
END="\033[0m"
while true;doecho -e "\033[33;1m\c"cat <<EOF1)鲍鱼2)满汉全席3)龙虾4)燕窝5)帝王蟹6)退出
EOF
echo -e "\033[0m"
read -p "请点菜:" MENU
case $MENU in
1|4)$COLOR'菜价:$10'$ENDlet sum+=10;;
3|5)$COLOR'菜价:$20'$ENDlet sum+=20;;
2)$COLOR'菜价:$1000'$ENDlet sum+=1000;;
6)$COLOR2"你点菜的总价格是$sum"$ENDbreak;;
*)echo "没有这道菜,请重新点单";;
esac
$COLOR2"你点的菜总价格是$sum"$END
done`
案例2:持续监控应用状态
#!/bin/bash
#Fuction:基于While循环持续监控应用状态
while true
doif systemctl status sshd | grep -q "active (running)";thenbreakfisleep 10
done
shift技术
在Linux操作系统中,shift命令是一种强大的工具,用于对脚本参数进行向左移动的处理。这种功能在应对参数数量不确定的场景时尤为有用,使得脚本能够依次遍历并处理每一个参数。鉴于其在参数处理中的重要性,shift命令在各种程序的启动脚本中得到了广泛应用。
当执行脚本程序时,如果需要逐个扫描并处理参数,shift命令便显得尤为关键。每当shift命令执行一次,参数序列便会向左移动一个位置,这意味着下一个参数将成为新的第一个参数。相应地, $ #变量记录着脚本后面跟随的参数总数,其值在每次shift调用后会减1。这种机制允许脚本开发人员通过迭代方式逐个处理参数,而无需预先知道参数的确切数量。
需要特别注意的是,那些已经通过shift移出处理范围的参数将不再可用。因此,在使用shift时,必须谨慎考虑参数处理的顺序和逻辑,以确保每个参数都在其有效范围内得到恰当处理。此外, $ n变量可以用来获取脚本后面跟随的第n个参数的具体值,进一步增强了脚本的灵活性和功能性。
[root@nqs22-tps22 ~]# type shift
shift 是 shell 内嵌
[root@nqs22-tps22 ~]# help shift
shift: shift [n]移位位置参数。重命名位置参数 $N+1、$N+2 ... 到 $1、$2 ... 如果没有给定 N,则假设为1.退出状态:返回成功,除非 N 为负或者大于 $#。
参考实例:依次读取输入的参数并打印参数个数:
[root@nqs22-tps22 ~]# vim run.sh
[root@nqs22-tps22 ~]# cat run.sh
#!/bin/bash
while [ $# != 0 ];do
echo "第一个参数为:$1,参数个数为:$#"
shift
done
[root@nqs22-tps22 ~]# sh run.sh a b c d e f
第一个参数为:a,参数个数为:6
第一个参数为:b,参数个数为:5
第一个参数为:c,参数个数为:4
第一个参数为:d,参数个数为:3
第一个参数为:e,参数个数为:2
第一个参数为:f,参数个数为:1
参考实例:将参数从左到右逐个移动
[root@nqs22-tps22 ~]# vim shift.sh
#!/bin/bash
while [ $# -ne 0 ]
do
echo "第一个参数为:$1 参数个数为:$#"
shift
done
[root@nqs22-tps22 ~]# sh shift.sh Lily Lucy Jake Mike
第一个参数为:Lily 参数个数为:4
第一个参数为:Lucy 参数个数为:3
第一个参数为:Jake 参数个数为:2
第一个参数为:Mike 参数个数为:1
[root@nqs22-tps22 ~]#