------ 导读 ------
近期有很多小伙伴们在公众号留言询问有关Shell脚本的内容。因此我们决定新增一个合集,专门分享Shell脚本编写的相关知识。除此之外我们还会更新一些在工作中常用的脚本和可复用的Shell函数,帮助大家更高效地解决实际问题。本篇主要介绍Shell循环语句相关,包括循环的基础知识、for循环之基于数值的循环、for循环之基于字符串的循环、while循环之死循环、while循环之读取文件内容、while循环之读取变量值、until循环之等待某进程启动、break和continue指令介绍等。
循环基础知识
在 Shell 脚本中,循环是一种控制结构,用于重复执行一段代码多次。常用的有 for 循环、while循环、until循环,三者之间的性能差异不是特别明显,主要取决于具体的使用场景和循环体的复杂性。for 循环用于遍历固定数量的元素(如数组、列表等),通常用在已知迭代次数的场景。while 循环在条件为真时执行,而 until 循环在条件为假时执行。这意味着它们在逻辑上是相对的,选择哪种循环更多是基于编程的可读性。
for 循环
for循环基础
for 循环的语法如下所示
*
*
*
*
for variable in listdo # 需要执行的命令done
示例: * * * * * * * * * * *
[root@imzcy ~]# cat f.sh#!/bin/bash
for VAR in 1 2 3 4 5do
echo "[$(date +%Y%m%d_%H%M%S)] 当前循环中 VAR 的值为:$VAR" sleep 1
done[root@imzcy ~]#
简单来说 for 循环会将 in 后面的所有空格分隔的内容都作为单个参数一个个读入进来顺序执行完。 * * * * * * *
[root@imzcy ~]# sh f.sh[20240817_113851] 当前循环中 VAR 的值为:1[20240817_113852] 当前循环中 VAR 的值为:2[20240817_113853] 当前循环中 VAR 的值为:3[20240817_113854] 当前循环中 VAR 的值为:4[20240817_113855] 当前循环中 VAR 的值为:5[root@imzcy ~]#
前面讲过 for 循环通常用在已知迭代次数的场景中,接下来我们给出一些实际案例。
for循环之基于数值的循环
主要用在想要循环指定次数的场景中,通过 seq 命令或者 {} 都可以实现。 通过 seq 命令 * * * * * * * * * * *
[root@imzcy ~]# cat f2.sh#!/bin/bash
for VAR in $( seq 1 5 )do
echo "[$(date +%Y%m%d_%H%M%S)] 当前循环中 VAR 的值为:$VAR" sleep 1
done[root@imzcy ~]#
[root@imzcy ~]# sh f2.sh[20240817_115517] 当前循环中 VAR 的值为:1[20240817_115518] 当前循环中 VAR 的值为:2[20240817_115519] 当前循环中 VAR 的值为:3[20240817_115520] 当前循环中 VAR 的值为:4[20240817_115521] 当前循环中 VAR 的值为:5[root@imzcy ~]#
通过 {} 实现 * * * * * * * * * * *
[root@imzcy ~]# cat f3.sh#!/bin/bash
for VAR in {1..5}do
echo "[$(date +%Y%m%d_%H%M%S)] 当前循环中 VAR 的值为:$VAR" sleep 1
done[root@imzcy ~]#
[root@imzcy ~]# sh f3.sh[20240817_115748] 当前循环中 VAR 的值为:1[20240817_115749] 当前循环中 VAR 的值为:2[20240817_115750] 当前循环中 VAR 的值为:3[20240817_115751] 当前循环中 VAR 的值为:4[20240817_115752] 当前循环中 VAR 的值为:5[root@imzcy ~]#
for循环之基于字符串的循环
主要用在想要循环所有已知字符的情况下,可以为空格分隔的多个字符串或命令的执行结果。
直接指定空格分隔的多个字符串生成json数据
*
*
*
*
*
*
*
*
*
*
*
*
[root@imzcy ~]# cat f5.sh#!/bin/bash
for VAR in zhangsan lisi wangwudo
JSON_DATA='{"name":"'"${VAR}"'","group":"test-001"}' echo "[$(date +%Y%m%d_%H%M%S)] 数据为 '$JSON_DATA'" sleep 1
done[root@imzcy ~]#
[root@imzcy ~]# sh f5.sh[20240817_122120] 数据为 '{"name":"zhangsan","group":"test-001"}'[20240817_122121] 数据为 '{"name":"lisi","group":"test-001"}'[20240817_122122] 数据为 '{"name":"wangwu","group":"test-001"}'[root@imzcy ~]#
使用命令的执行结果创建备份文件
*
*
*
[root@imzcy ~]# ls *.txtexample.txt group.txt test.txt user.txt[root@imzcy ~]#
[root@imzcy ~]# cat f4.sh#!/bin/bash
for VAR in $( ls /root/*.txt )do
NEW_VAR="${VAR}.bak" echo "[$(date +%Y%m%d_%H%M%S)] $VAR 文件将备份为 $NEW_VAR" cp $VAR $NEW_VAR sleep 1
done[root@imzcy ~]#
[root@imzcy ~]# sh f4.sh[20240817_120453] /root/example.txt 文件将备份为 /root/example.txt.bak[20240817_120454] /root/group.txt 文件将备份为 /root/group.txt.bak[20240817_120455] /root/test.txt 文件将备份为 /root/test.txt.bak[20240817_120456] /root/user.txt 文件将备份为 /root/user.txt.bak[root@imzcy ~]#[root@imzcy ~]# ls *.bakexample.txt.bak group.txt.bak test.txt.bak user.txt.bak[root@imzcy ~]#
while 循环
while 循环基础while 循环在条件为真时执行,语法格式如下所示 * * * *
while [ 条件 ]do # 需要执行的命令done
示例 * * * * * * * * * * * *
[root@imzcy ~]# cat f6.sh#!/bin/bash
VAR=1while [ $VAR -le 5 ]do
echo "VAR: $VAR" ((VAR++))
done[root@imzcy ~]#
变量VAR的初始值为1,每次循环后自增1,当循环了5次后值为6的时候,条件 VAR 值小于等于5 不为真,则退出循环。
*
*
*
*
*
*
*
[root@imzcy ~]# sh f6.shVAR: 1VAR: 2VAR: 3VAR: 4VAR: 5[root@imzcy ~]#
while 循环之死循环 前面讲过 while 循环只在 "条件" 为真的时候运行,所以如果想要循环一直持续执行的话,最简单就是条件直接为 true 。
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
[root@imzcy ~]# cat f7.sh#!/bin/bash
while truedo
ping -c 1 -W 2 example.com >/dev/null 2>&1 if [ $? -ne 0 ] ; then
echo "[ $(date +%Y%m%d_%H%M%S) ] 到 example.com 的网络不通!"
fi sleep 1
done[root@imzcy ~]#
while 循环之读取文件内容 while 循环还可以用于读取文件中的内容,进行数据处理。 * * * * * * * * * * * * * * * * * * * * *
[root@imzcy ~]# cat ip.txt192.168.2.11 1723940173192.168.2.12 1723942573192.168.2.13 1723947373[root@imzcy ~]#[root@imzcy ~]# cat f8.sh#!/bin/bash
INPUT_FILE=/root/ip.txt
while read NOW_LINEdo
echo "当前行为:$NOW_LINE" eval $(echo "$NOW_LINE" |awk '{printf("KEY_IP=%s; KEY_TIME=%s",$1,$2)}') NEW_TIME=$( date -d @${KEY_TIME} "+%Y-%m-%d %H:%M:%S" ) echo "将时间戳转换为标准时间后行为:[$NEW_TIME] $KEY_IP" sleep 1
done < $INPUT_FILE[root@imzcy ~]#
[root@imzcy ~]# sh f8.sh当前行为:192.168.2.11 1723940173处理后数据为:[2024-08-18 08:16:13] 192.168.2.11当前行为:192.168.2.12 1723942573处理后数据为:[2024-08-18 08:56:13] 192.168.2.12当前行为:192.168.2.13 1723947373处理后数据为:[2024-08-18 10:16:13] 192.168.2.13[root@imzcy ~]#
while 循环之读取变量值
如果变量的值为多行内容,也可以直接 while 循环读取进行处理。 * * * * * * * * * * * * * * * *
[root@imzcy ~]# cat f9.sh#!/bin/bash
USER_LIST="张三 zhangsan@example.com李四 lisi@example.com王五 wangwu@example.com"
echo "$USER_LIST" | while read NOW_LINEdo
eval $(echo "$NOW_LINE" |awk '{printf("KEY_USER=%s; KEY_MAIL=%s",$1,$2)}') echo "即将给用户 ${KEY_USER} 的邮箱 ${KEY_MAIL} 发送邮件。" sleep 1
done[root@imzcy ~]#
[root@imzcy ~]# sh f9.sh即将给用户 张三 的邮箱 zhangsan@example.com 发送邮件。即将给用户 李四 的邮箱 lisi@example.com 发送邮件。即将给用户 王五 的邮箱 wangwu@example.com 发送邮件。[root@imzcy ~]#
使用上面的方式读取变量中的内容处理数据时,由于使用了管道符号 | ,因此会创建一个新的子shell来执行管道右侧的命令,子shell中所做的任何更改(包括变量赋值)都不会影响到主shell。所以这种情况下,while 循环中任何的变量赋值以及变量内容的修改都无法传递到循环外的后续命令的执行。这点需要额外注意。
until 循环
until 循环基础
until 循环在条件为假时执行,语法格式如下所示 * * * *
until [ 条件 ]do # 需要执行的命令done
示例 * * * * * * * * * * * *
[root@imzcy ~]# cat f10.sh#!/bin/bash
VAR=0until [ $VAR -eq 8 ]do
read -p "请输入您猜测的数字:" VAR sleep 1
done[root@imzcy ~]#
当用户输入错误的数字时会一直要求重新输入,直到输入正确的数字时才会退出循环。
*
*
*
*
*
*
[root@imzcy ~]# sh f10.sh请输入您猜测的数字:1请输入您猜测的数字:2请输入您猜测的数字:6请输入您猜测的数字:8[root@imzcy ~]#
正常情况下一般 while 循环用的比较多一些,但是当你需要等待某个条件变为真时,使用 until 循环是一个理想的选择。例如,等待某个进程启动,或者等待某个文件的存在。
until 循环之等待某进程启动
如下脚本使用 pgrep 命令作为条件,当指定名称的进程没有在运行时,会一直循环等待直到条件为真时结束循环。 * * * * * * * * * * * * *
[root@imzcy ~]# cat f11.sh#!/bin/bash
PROCESS_NAME="$1"
until pgrep -x "$PROCESS_NAME" > /dev/nulldo
echo "进程 $PROCESS_NAME 未运行。请等待此进程启动!" sleep 1
done[root@imzcy ~]#
由于不存在 dotnet 进程,所以一直循环直到我 <ctrl>+<C> 终止。 * * * * * *
[root@imzcy ~]# sh f11.sh dotnet进程 dotnet 未运行。请等待此进程启动!进程 dotnet 未运行。请等待此进程启动!进程 dotnet 未运行。请等待此进程启动!^C[root@imzcy ~]#
由于 mysqld 进程已经在启动了,所以循环直接退出执行了。 * *
[root@imzcy ~]# sh f11.sh mysqld[root@imzcy ~]#
break 和 continue 指令
上面讲到的三个循环语句中有两个通用的指令,break 和 continue。break 用于中止当前循环的执行。当 break 被执行时,循环将被立即终止,程序的控制权将转移到循环后面的代码。而 continue 用于跳过当前循环的剩余部分,并立即开始下一次的循环迭代。也就是说,当 continue 被执行时,控制权将转到循环的条件部分,而不执行随后的代码。
循环之 break 指令测试* * * * * * * * * *
[root@imzcy ~]# cat f12-1.sh#!/bin/bash
for i in {1..10}; do if [ $i -eq 5 ]; then break fi echo $idone[root@imzcy ~]#
如下所示,当 i 的值循环到 5 时,则循环被终止。 * * * * * *
[root@imzcy ~]# sh f12-1.sh1234[root@imzcy ~]#
循环之 continue 指令测试* * * * * * * * * *
[root@imzcy ~]# cat f12-2.sh#!/bin/bash
for i in {1..10}; do if [ $i -eq 5 ]; then continue fi echo $idone[root@imzcy ~]#
如下所示,当 i 的值循环到 5 时,则跳过循环中后续命令的执行(没有打印数字5)直接开始下次循环。 * * * * * * * * * * *
[root@imzcy ~]# sh f12-2.sh1234678910[root@imzcy ~]#