Bash 循环结构
循环结构使你能够自动化重复性任务、高效处理数据,并创建更具动态和更强大的脚本。
在本章中,我们将深入探讨 Bash 中可用的三种主要循环结构:for、while 和 until。我们将探索它们的语法、功能和实际应用,使你具备在 Bash 脚本中高效利用它们的技能。
1. for 循环
for 循环专为遍历一系列项目(序列)而设计。这个序列可以是字符串列表、数字范围或命令的输出结果。Bash 提供了几种不同形式的 for 循环,每种都适用于不同的场景。
1.1 基础 for 循环与列表
最常见的 for 循环形式是遍历一个项目列表。
for item in item1 item2 item3 ... itemN
do
# 针对每个项目执行的代码
echo "正在处理项目: $item"
done语法解析:
for item in item1 item2 ... itemN: 这一行启动了循环。它会依次遍历提供的列表(item1、item2等)中的每个项目。在每次循环迭代时,变量item都会被赋予列表中当前元素的值。do: 标记循环体(需要执行的代码块)的开始。- echo "正在处理项目: $item": 这是针对列表中的每个项目都要执行的代码。在这里,我们只是向控制台打印一条消息,但你可以包含任何有效的 Bash 命令。
$item属于变量替换,它会被变量item的当前值所取代。 done: 标记循环体的结束。
示例:
#!/bin/bash
for fruit in apple banana cherry
do
echo "我喜欢 $fruit"
done输出:
我喜欢 apple
我喜欢 banana
我喜欢 cherry1.2 结合大括号扩展的 for 循环
大括号扩展(Brace expansion)提供了一种生成数字或字符序列的便捷方式。
for i in {1..5}
do
echo "数字: $i"
done语法解析:
{1..5}: 这就是大括号扩展。它会生成一个从 1 到 5(包含 1 和 5)的数字序列。Bash 会在循环开始之前就将它展开,因此这个循环实际上等同于for i in 1 2 3 4 5。
输出:
数字: 1
数字: 2
数字: 3
数字: 4
数字: 5你还可以指定一个步长值(Step value):
for i in {1..10..2}
do
echo "数字: $i"
done语法解析:
{1..10..2}: 这会生成一个从 1 到 10 的数字序列,每次递增步长为 2。因此,生成的序列将是 1, 3, 5, 7, 9。
输出:
数字: 1
数字: 3
数字: 5
数字: 7
数字: 91.3 结合命令替换的 for 循环
命令替换(Command substitution)允许你将一个命令的输出结果作为 for 循环的遍历列表。
for file in $(ls *.txt)
do
echo "正在处理文件: $file"
done语法解析:
$(ls *.txt): 这是命令替换。Bash 会执行ls *.txt命令,并将代码的这一部分替换为该命令的输出。在这个例子中,ls *.txt会列出当前目录下所有扩展名为.txt的文件。然后,for循环会遍历这些文件名。
示例:
假设当前目录下有以下文件:file1.txt、file2.txt、image.png、data.txt。
输出:
正在处理文件: file1.txt
正在处理文件: file2.txt
正在处理文件: data.txt重要提示: 虽然命令替换可以工作,但在 for 循环中直接使用通配符(Globbing)通常是更好的选择(参见下一节),因为它能更好地处理带有空格或特殊字符的文件名。
1.4 结合通配符的 for 循环
通配符(Globbing)提供了一种强大的方式,可以根据模式匹配文件名。
for file in *.txt
do
echo "正在处理文件: $file"
done语法解析:
*.txt: 这是一个通配符模式。*匹配任何字符序列。因此,*.txt匹配任何以.txt结尾的文件。在循环开始之前,Bash 会将这个模式扩展为匹配的文件列表。这种方法通常比使用 ls 进行命令替换更安全、更可靠,尤其是当处理包含空格或特殊字符的文件名时。
示例:
假设文件与上一个例子相同(file1.txt、file2.txt、image.png、data.txt):
输出:
正在处理文件: file1.txt
正在处理文件: file2.txt
正在处理文件: data.txt1.5 C 语言风格的 for 循环
Bash 也支持 C 语言风格的 for 循环,这在进行数值迭代时非常有用。
for (( i=1; i<=5; i++ ))
do
echo "数字: $i"
done语法解析:
(( i=1; i<=5; i++ )): 这一部分定义了循环的控制结构。i=1: 将循环计数器 i 初始化为 1。i<=5: 这是循环条件。只要 i 小于或等于 5,循环就会继续执行。i++: 在每次迭代之后,将循环计数器 i 的值递增 1。
输出:
数字: 1
数字: 2
数字: 3
数字: 4
数字: 51.6 课后练习:for 循环
- 打印数字列表: 使用
for循环和大括号扩展打印从 10 到 20 的数字。 - 处理目录中的文件: 创建一个脚本,遍历目录中的所有
.jpg文件,并打印它们的名称和大小(使用ls -l命令)。记住要正确处理带有空格的文件名。 - 计算总和: 使用 C 语言风格的
for循环计算从 1 到 100 的数字总和。
2. while 循环
只要给定的条件为真(true),while 循环就会不断执行一个代码块。
while [ condition ]
do
# 当条件为真时要执行的代码
# 重要提示:一定要包含能让条件最终变为假的代码,以避免死循环
done语法解析:
while [ condition ]: 这一行启动了while循环。只要方括号内的condition(条件)为真,循环内的代码就会继续执行。[ 实际上是一个等同于test的命令,请确保方括号两边有空格。do: 标记循环体的开始。done: 标记循环体的结束。condition: 这是一个计算结果为真或假的表达式。它可以包含变量比较、文件存在性检查或任何其他有效的 Bash 表达式。
2.1 示例:倒计时器
#!/bin/bash
count=5
while [ $count -gt 0 ]
do
echo "倒计时: $count"
count=$((count - 1)) # 递减 count 的值
sleep 1 # 等待 1 秒
done
echo "发射!"语法解析:
count=5: 将变量count初始化为 5。while [ $count -gt 0 ]: 这是循环条件。-gt是“大于”运算符。只要count的值大于 0,循环就会继续。echo "倒计时: $count": 打印count的当前值。count=$((count - 1)): 将count的值递减 1。$((...))在 Bash 中用于算术扩展,它会计算括号内的表达式。sleep 1: 暂停脚本执行 1 秒钟。这是为了创造一个可见的倒计时效果。echo "发射!": 这一行在循环完成后(当count不再大于 0 时)执行。
输出:
倒计时: 5
倒计时: 4
倒计时: 3
倒计时: 2
倒计时: 1
发射!2.2 示例:逐行读取文件
#!/bin/bash
file="mydata.txt"
# 如果文件不存在,则创建它并写入一些内容
if [ ! -f "$file" ]; then
echo "Line 1" > "$file"
echo "Line 2" >> "$file"
echo "Line 3" >> "$file"
fi
while IFS= read -r line
do
echo "读取到: $line"
done < "$file"语法解析:
file="mydata.txt": 将变量file设置为我们要读取的文件名。if语句检查文件是否存在,如果不存在则创建它并填充一些示例内容。while IFS= read -r line:IFS=: 将内部字段分隔符(Internal Field Separator, IFS)设置为空字符串。这非常重要,它可以防止read命令剥离行首和行尾的空白字符。read -r line: 从输入中读取一行并将其存储在变量line中。-r选项可以防止反斜杠转义被解释。done < "$file": 这会将mydata.txt的内容重定向到while循环的标准输入中。循环随后会逐行读取该文件。
输出(假设 mydata.txt 在不同的行包含 "Line 1"、"Line 2" 和 "Line 3"):
读取到: Line 1
读取到: Line 2
读取到: Line 3重要提示:while IFS= read -r line 结构是 Bash 中逐行读取文件的推荐方法。它能正确处理空白字符和反斜杠,防止出现意外行为。
3. until 循环
until 循环与 while 循环恰好相反。只要给定的条件为假(false),它就会执行代码块。当条件变为真时,循环终止。
until [ condition ]
do
# 当条件为假时要执行的代码
# 重要提示:一定要包含能让条件最终变为真的代码,以避免死循环
done语法解析:
until [ condition ]: 这一行启动了until循环。只要方括号内的condition为假,循环内的代码就会继续执行。do: 标记循环体的开始。done: 标记循环体的结束。condition: 这是一个计算结果为真或假的表达式。循环会一直继续,直到(until)这个条件变为真。
3.1 示例:等待文件被创建
#!/bin/bash
file="myfile.txt"
until [ -f "$file" ]
do
echo "等待 $file 被创建..."
sleep 5 # 等待 5 秒
done
echo "$file 已创建。继续执行。"语法解析:
file="myfile.txt": 将变量file设置为我们正在等待的文件名。until [ -f "$file" ]: 这是循环条件。-f是一个测试运算符,用于检查文件是否存在。只要myfile.txt文件不存在,循环就会继续执行。echo "等待 $file 被创建...": 打印一条消息,表明脚本正在等待文件创建。sleep 5: 暂停脚本执行 5 秒钟。echo "$file 已创建。继续执行。": 这一行在循环完成后(即myfile.txt存在时)执行。
输出(根据文件何时被创建,输出行数会有所不同):
等待 myfile.txt 被创建...
等待 myfile.txt 被创建...
myfile.txt 已创建。继续执行。3.2 示例:向上递增计数
#!/bin/bash
target=10
current=1
until [ $current -gt $target ]
do
echo "当前值: $current"
current=$((current + 1))
done
echo "达到目标值!"语法解析:
target=10: 设置目标值为 10。current=1: 将当前值初始化为 1。until [ $current -gt $target ]: 循环会一直继续,直到当前值大于目标值。- 循环在每次迭代中将
current变量递增 1,并打印它的值。
输出:
当前值: 1
当前值: 2
当前值: 3
当前值: 4
当前值: 5
当前值: 6
当前值: 7
当前值: 8
当前值: 9
当前值: 10
达到目标值!4. 实战案例:将循环结构应用于系统管理
回想一下模块 1 中介绍的系统管理脚本。我们可以利用循环结构对其进行增强。假设我们想要监控多个目录的磁盘空间使用情况,并在其中任何一个超过设定阈值时发出报告。
#!/bin/bash
# 定义要监控的目录和阈值
directories="/var /tmp /home"
threshold=90 # 百分比
# 遍历每个目录
for dir in $directories
do
# 获取磁盘空间使用百分比
usage=$(df -h "$dir" | awk 'NR==2 {print $5}' | tr -d '%')
# 检查使用率是否超过阈值
if [ "$usage" -gt "$threshold" ]; then
echo "警告: $dir 的磁盘使用率为 $usage%,超过了 $threshold% 的阈值。"
fi
done语法解析:
directories="/var /tmp /home": 定义一个包含要监控的目录列表的变量,目录之间用空格分隔。threshold=90: 将磁盘空间使用率的报警阈值设置为 90%。for dir in $directories: 遍历$directories变量中的每个目录。usage=$(df -h "$dir" | awk 'NR==2 {print $5}' | tr -d '%'):df -h "$dir": 以人类可读的格式获取当前目录的磁盘空间使用信息。awk 'NR==2 {print $5}': 从df -h输出的第二行中提取第五列内容,该列正是使用率百分比。NR==2选中df命令输出的第二行(包含使用数据的那一行),{print $5}打印该行的第五列。tr -d '%': 移除使用率百分比中的%符号。if [ "$usage" -gt "$threshold" ]: 检查使用率是否大于阈值。echo "警告: ...": 如果超过阈值,则打印一条警告消息。