Shell流程控制概述
流程控制是编程语言中的核心概念,它允许程序根据条件执行不同的代码块,或者重复执行某些代码块。
在Shell脚本中,流程控制主要包括两大类:
- 条件判断 - 根据条件决定执行哪部分代码
- 循环控制 - 重复执行某段代码直到满足特定条件
学习提示
Shell脚本中的流程控制结构与大多数编程语言类似,但语法有些不同。掌握这些结构是编写高效Shell脚本的关键。
流程控制结构概览
条件判断
→
if语句
case语句
循环控制
→
for循环
while循环
until循环
每种结构都有其特定的使用场景和语法规则,下面将详细介绍。
条件判断
if语句
if语句用于根据条件执行不同的代码块。在Shell中,if语句的基本语法如下:
基本语法
if [ 条件 ]
then
# 条件为真时执行的代码
elif [ 其他条件 ]
then
# 其他条件为真时执行的代码
else
# 所有条件都为假时执行的代码
fi
if语句的特点
- 使用
if开始,以fi结束 - 条件测试使用方括号
[ ]或[[ ]] - 可以使用
elif添加多个条件分支 else分支是可选的
示例1: 简单的if语句
#!/bin/bash
# 检查文件是否存在
if [ -f "myfile.txt" ]
then
echo "文件存在"
else
echo "文件不存在"
fi
示例2: 使用elif的多条件判断
#!/bin/bash
# 根据分数判断等级
score=85
if [ $score -ge 90 ]
then
echo "优秀"
elif [ $score -ge 80 ]
then
echo "良好"
elif [ $score -ge 70 ]
then
echo "中等"
elif [ $score -ge 60 ]
then
echo "及格"
else
echo "不及格"
fi
提示: 在Shell中,条件测试时方括号内需要留空格,如
[ $var -eq 10 ]。
case语句
case语句用于基于模式匹配的多分支选择,比多个if-elif语句更简洁。
基本语法
case 变量 in
模式1)
# 匹配模式1时执行的代码
;;
模式2)
# 匹配模式2时执行的代码
;;
*)
# 默认情况执行的代码
;;
esac
case语句的特点
- 使用
case开始,以esac结束 - 每个分支以
)开始,以;;结束 - 可以使用通配符进行模式匹配
*表示默认情况
示例1: 简单的case语句
#!/bin/bash
# 根据用户输入执行不同操作
echo "请输入操作(start|stop|restart|status):"
read operation
case $operation in
start)
echo "启动服务..."
;;
stop)
echo "停止服务..."
;;
restart)
echo "重启服务..."
;;
status)
echo "查看服务状态..."
;;
*)
echo "未知操作: $operation"
;;
esac
示例2: 使用模式匹配
#!/bin/bash
# 根据文件扩展名处理文件
filename="document.pdf"
case $filename in
*.txt)
echo "文本文件: $filename"
;;
*.pdf|*.doc|*.docx)
echo "文档文件: $filename"
;;
*.jpg|*.png|*.gif)
echo "图像文件: $filename"
;;
*)
echo "未知文件类型: $filename"
;;
esac
提示: case语句中的模式匹配支持通配符,如
*匹配任意字符,?匹配单个字符。
条件测试
在Shell中,条件测试用于判断表达式是否为真。可以使用test命令或方括号[ ]。
文件测试操作符
| 操作符 | 说明 |
|---|---|
-e file |
文件存在 |
-f file |
文件存在且是普通文件 |
-d file |
文件存在且是目录 |
-r file |
文件存在且可读 |
-w file |
文件存在且可写 |
-x file |
文件存在且可执行 |
字符串测试操作符
| 操作符 | 说明 |
|---|---|
str1 = str2 |
字符串相等 |
str1 != str2 |
字符串不相等 |
-z str |
字符串长度为0 |
-n str |
字符串长度不为0 |
数值测试操作符
| 操作符 | 说明 |
|---|---|
num1 -eq num2 |
等于 |
num1 -ne num2 |
不等于 |
num1 -lt num2 |
小于 |
num1 -le num2 |
小于等于 |
num1 -gt num2 |
大于 |
num1 -ge num2 |
大于等于 |
条件测试示例
#!/bin/bash
# 文件测试
if [ -f "/etc/passwd" ]; then
echo "文件存在"
fi
# 字符串测试
name="Alice"
if [ "$name" = "Alice" ]; then
echo "Hello Alice"
fi
# 数值测试
count=10
if [ $count -gt 5 ]; then
echo "计数大于5"
fi
循环控制
for循环
for循环用于遍历列表中的每个元素,并对每个元素执行相同的操作。
基本语法
for 变量 in 列表
do
# 循环体
done
for循环的特点
- 使用
for开始,以done结束 - 列表可以是显式列表、变量或命令输出
- C语言风格的for循环在Bash中也支持
- 常用于遍历文件、数组或命令输出
示例1: 遍历列表
#!/bin/bash
# 遍历显式列表
for fruit in apple banana orange
do
echo "水果: $fruit"
done
示例2: 遍历文件
#!/bin/bash
# 遍历当前目录下的所有.txt文件
for file in *.txt
do
echo "处理文件: $file"
# 对每个文件执行操作
done
示例3: C语言风格的for循环
#!/bin/bash
# C语言风格的for循环
for (( i=1; i<=5; i++ ))
do
echo "计数: $i"
done
while循环
while循环在条件为真时重复执行代码块。
基本语法
while [ 条件 ]
do
# 循环体
done
while循环的特点
- 使用
while开始,以done结束 - 每次循环前检查条件
- 如果条件一开始就为假,循环体一次也不执行
- 常用于读取文件或等待某个条件满足
示例1: 计数器
#!/bin/bash
# 使用while循环实现计数器
count=1
while [ $count -le 5 ]
do
echo "计数: $count"
count=$((count + 1))
done
示例2: 读取文件内容
#!/bin/bash
# 逐行读取文件
while IFS= read -r line
do
echo "行内容: $line"
done < "filename.txt"
示例3: 无限循环
#!/bin/bash
# 无限循环(使用true命令)
while true
do
echo "循环中..."
sleep 1
done
until循环
until循环在条件为假时重复执行代码块,与while循环相反。
基本语法
until [ 条件 ]
do
# 循环体
done
until循环的特点
- 使用
until开始,以done结束 - 每次循环前检查条件
- 如果条件一开始就为真,循环体一次也不执行
- 常用于等待某个条件变为真
示例1: 等待服务启动
#!/bin/bash
# 等待服务启动
until systemctl is-active --quiet nginx
do
echo "等待nginx服务启动..."
sleep 2
done
echo "nginx服务已启动"
示例2: 计数器
#!/bin/bash
# 使用until循环实现计数器
count=1
until [ $count -gt 5 ]
do
echo "计数: $count"
count=$((count + 1))
done
提示: until循环与while循环的主要区别在于条件判断。until循环在条件为假时执行,而while循环在条件为真时执行。
循环控制语句
在循环中,有时需要提前结束循环或跳过某次循环。Shell提供了break和continue语句来实现这些功能。
break语句
break语句用于立即退出循环,不再执行循环中剩余的代码。
break示例
#!/bin/bash
# 在找到特定元素时退出循环
for num in 1 2 3 4 5 6 7 8 9 10
do
if [ $num -eq 5 ]
then
echo "找到数字5,退出循环"
break
fi
echo "当前数字: $num"
done
continue语句
continue语句用于跳过当前循环的剩余代码,直接开始下一次循环。
continue示例
#!/bin/bash
# 跳过偶数,只打印奇数
for num in 1 2 3 4 5 6 7 8 9 10
do
if [ $((num % 2)) -eq 0 ]
then
continue
fi
echo "奇数: $num"
done
break和continue的层级控制
在嵌套循环中,可以指定break或continue要影响哪一层循环。
break N示例
#!/bin/bash
# 嵌套循环中的break
for i in 1 2 3
do
echo "外层循环: $i"
for j in 1 2 3
do
echo "内层循环: $j"
if [ $j -eq 2 ]
then
break 2 # 退出两层循环
fi
done
done
continue N示例
#!/bin/bash
# 嵌套循环中的continue
for i in 1 2 3
do
echo "外层循环: $i"
for j in 1 2 3
do
if [ $j -eq 2 ]
then
continue 2 # 继续外层循环的下一次迭代
fi
echo "内层循环: $j"
done
done
提示: break和continue后面可以跟一个数字N,表示要影响第N层循环。默认情况下,N=1,表示只影响当前循环。
实战示例
示例1: 系统监控脚本
使用流程控制创建一个系统监控脚本,检查系统资源使用情况。
#!/bin/bash
# 检查CPU使用率
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
if [ $(echo "$cpu_usage > 80" | bc) -eq 1 ]
then
echo "警告: CPU使用率过高 - $cpu_usage%"
else
echo "CPU使用率正常 - $cpu_usage%"
fi
# 检查内存使用率
mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}')
if [ $(echo "$mem_usage > 90" | bc) -eq 1 ]
then
echo "警告: 内存使用率过高 - $mem_usage%"
else
echo "内存使用率正常 - $mem_usage%"
fi
示例2: 文件备份脚本
使用循环和条件判断创建一个文件备份脚本。
#!/bin/bash
# 备份目录
backup_dir="/backup"
source_dir="/home/user/documents"
# 创建备份目录
if [ ! -d "$backup_dir" ]
then
mkdir -p "$backup_dir"
fi
# 备份文件
for file in "$source_dir"/*
do
if [ -f "$file" ]
then
filename=$(basename "$file")
backup_file="$backup_dir/${filename}.backup"
if [ ! -f "$backup_file" ] || [ "$file" -nt "$backup_file" ]
then
cp "$file" "$backup_file"
echo "已备份: $filename"
else
echo "跳过: $filename (已是最新)"
fi
fi
done
示例3: 用户管理脚本
使用case语句创建一个用户管理脚本。
#!/bin/bash
# 用户管理脚本
echo "用户管理菜单:"
echo "1. 添加用户"
echo "2. 删除用户"
echo "3. 修改用户"
echo "4. 列出用户"
echo "请输入选择(1-4):"
read choice
case $choice in
1)
echo "请输入用户名:"
read username
sudo useradd "$username"
echo "用户 $username 已添加"
;;
2)
echo "请输入要删除的用户名:"
read username
sudo userdel "$username"
echo "用户 $username 已删除"
;;
3)
echo "请输入要修改的用户名:"
read username
sudo usermod -c "修改的用户" "$username"
echo "用户 $username 已修改"
;;
4)
echo "系统用户列表:"
cut -d: -f1 /etc/passwd | sort
;;
*)
echo "无效选择"
;;
esac
示例4: 日志分析脚本
使用循环和条件判断分析日志文件。
#!/bin/bash
# 日志分析脚本
log_file="/var/log/syslog"
error_count=0
warning_count=0
# 分析日志文件
while IFS= read -r line
do
case $line in
*error*|*ERROR*)
echo "错误: $line"
((error_count++))
;;
*warning*|*WARNING*)
echo "警告: $line"
((warning_count++))
;;
esac
done < "$log_file"
echo "分析完成:"
echo "错误数量: $error_count"
echo "警告数量: $warning_count"