Shell 脚本控制结构:if、for、while
Shell 脚本的核心能力在于自动化处理重复任务。掌握条件判断和循环控制,就掌握了脚本编程的半壁江山。本文手把手讲解 if、for、while 三大控制结构的完整语法和实战用法,帮助你从入门到实战。
1 条件判断:if 语句
if 语句用于根据条件执行不同分支。它是 Shell 脚本中最基础也是最重要的控制结构。
1.1 基本语法
if [ 条件 ]; then
# 条件成立时执行的命令
fi
注意:[ 和 ] 是测试命令,两侧必须保留空格。fi 是 if 的反向拼写,表示条件结束。
1.2 带分支的写法
if [ 条件 ]; then
# 条件成立时执行的命令
else
# 条件不成立时执行的命令
fi
1.3 多分支写法
if [ 条件1 ]; then
# 条件1成立时执行
elif [ 条件2 ]; then
# 条件2成立时执行
else
# 所有条件都不成立时执行
fi
1.4 常用比较运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
-eq |
等于(数值) | [ $a -eq 5 ]` |
| `-ne` | 不等于(数值) | `[ $a -ne 0 ] |
-gt |
大于(数值) | [ $a -gt 10 ]` |
| `-ge` | 大于等于(数值) | `[ $a -ge 5 ] |
-lt |
小于(数值) | [ $a -lt 3 ]` |
| `-le` | 小于等于(数值) | `[ $a -le 8 ] |
= |
等于(字符串) | [ "$name" = "admin" ]` |
| `!=` | 不等于(字符串) | `[ "$status" != "ok" ] |
-z |
字符串为空 | [ -z "$input" ]` |
| `-n` | 字符串非空 | `[ -n "$path" ] |
-f |
文件存在且是普通文件 | [ -f "/etc/config" ] |
-d |
目录存在 | [ -d "/tmp/logs" ] |
-x |
文件可执行 | `[ -x "$script" ]` | ### 1.5 复合条件 使用 `-a` 表示"与",`-o` 表示"或",或使用 `&&` 和 `||`。 ```bash # 使用 -a 和 -o if [ $age -ge 18 -a $age -lt 60 ]; then echo "成年人" fi # 使用 && 和 ||(更推荐) if [ $status = "ok" ] && [ $count -gt 0 ]; then echo "状态正常且有数据" fi if [ -f "$file" ] |
elif [ $MEMORY -gt 500 ]; then
echo "注意:内存使用率中等,当前使用 ${MEMORY}MB"
else
echo "正常:内存使用率良好,当前使用 ${MEMORY}MB"
fi
```
---
## 2 循环结构:for 循环
`for` 循环用于**已知迭代次数**或**遍历集合**的场景。它逐个处理列表中的元素,结构清晰。
### 2.1 基本语法:遍历列表
```bash
for 变量 in 项目1 项目2 项目3; do
echo "当前处理: $变量"
done
### 2.2 使用花括号生成序列
```bash
# 遍历 1 到 5
for i in {1..5}; do
echo "数字: $i"
done
# 遍历 0 到 10,步长为 2
for i in {0..10..2}; do
echo "偶数: $i"
done
2.3 C 风格 for 循环
for ((i=1; i<=5; i++)); do
echo "第 $i 次循环"
done
```
### 2.4 遍历文件和目录
```bash
# 遍历当前目录下的所有文件
for file in *; do
echo "文件: $file"
done
# 遍历特定目录
for conf in /etc/*.conf; do
echo "配置文件: $conf"
done
# 遍历所有参数
for arg in "$@"; do
echo "参数: $arg"
done
```
### 2.5 实战示例:批量处理文件
```bash
#!/bin/bash
# 备份所有 .txt 文件
for txtfile in *.txt; do
if [ -f "$txtfile" ]; then
cp "$txtfile" "${txtfile%.txt}_backup.txt"
echo "已备份: $txtfile"
fi
done
```
### 2.6 for 循环与数组
```bash
# 定义数组
services=("nginx" "mysql" "redis")
# 遍历数组
for service in "${services[@]}"; do
systemctl status "$service" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "$service 正在运行"
fi
done
```
---
## 3 循环结构:while 循环
`while` 循环用于**条件驱动**的场景。只要条件为真,就持续执行循环体。适合处理未知迭代次数的任务。
### 3.1 基本语法
```bash
while [ 条件 ]; do
# 循环执行的命令
done
```
### 3.2 实战示例:读取文件逐行处理
```bash
#!/bin/bash
# 逐行读取 users.txt
while IFS= read -r line; do
echo "用户: $line"
done < users.txt
IFS= 防止行首空格被丢弃,-r 防止反斜杠被解释。
3.3 计数器模式
#!/bin/bash
count=1
while [ $count -le 10 ]; do
echo "计数: $count"
count=$((count + 1))
done
```
### 3.4 无限循环与退出
```bash
#!/bin/bash
# 无限循环(按 Ctrl+C 退出)
while true; do
echo "系统运行中..."
sleep 60
done
```
结合 `break` 和 `continue` 控制循环:
```bash
#!/bin/bash
for i in {1..10}; do
if [ $i -eq 3 ]; then
continue # 跳过 3
fi
if [ $i -eq 7 ]; then
break # 退出循环
fi
echo "数字: $i"
done
输出结果:1、2、4、5、6。
3.5 实战示例:监控服务直到启动
#!/bin/bash
service_name="nginx"
max_attempts=10
attempt=0
while [ $attempt -lt $max_attempts ]; do
if systemctl is-active --quiet "$service_name"; then
echo "$service_name 已启动"
exit 0
fi
attempt=$((attempt + 1))
echo "等待启动... (尝试 $attempt/$max_attempts)"
sleep 2
done
echo "启动失败:$service_name"
exit 1
4 高级技巧与最佳实践
4.1 read 命令结合循环
#!/bin/bash
echo "输入 'quit' 退出"
while read -p "请输入命令: " cmd; do
if [ "$cmd" = "quit" ]; then
break
fi
echo "执行: $cmd"
done
4.2 使用管道时的陷阱
注意:管道会创建子 Shell,变量修改不会影响父 Shell。
#!/bin/bash
count=0
cat file.txt | while read line; do
count=$((count + 1)) # 这个修改不会生效
done
echo "行数: $count" # 输出 0
正确做法是使用进程替换或重定向:
#!/bin/bash
count=0
while read line; do
count=$((count + 1))
done < <(cat file.txt)
echo "行数: $count" # 正确输出
4.3 循环中的错误处理
#!/bin/bash
for file in /data/*.csv; do
[ -f "$file" ] || continue # 跳过不存在的文件
if ! process_file "$file"; then
echo "处理失败: $file" >&2
continue
fi
echo "处理成功: $file"
done
5 综合实战:自动化部署脚本
#!/bin/bash
# 自动化部署脚本示例
DEPLOY_DIR="/opt/app"
BACKUP_DIR="/opt/backup"
TIMESTAMP=$(date +%Y%m%d%H%M%S)
# 步骤 1:检查必要条件
if [ ! -d "$DEPLOY_DIR" ]; then
echo "创建部署目录: $DEPLOY_DIR"
mkdir -p "$DEPLOY_DIR"
fi
# 步骤 2:备份旧版本
if [ -d "$DEPLOY_DIR/current" ]; then
backup_name="backup_$TIMESTAMP"
cp -a "$DEPLOY_DIR/current" "$BACKUP_DIR/$backup_name"
echo "已备份到: $BACKUP_DIR/$backup_name"
fi
# 步骤 3:下载并解压新版本
for arch in x86_64 aarch64; do
filename="app-$arch"
url="https://example.com/downloads/$filename"
echo "下载: $filename"
if wget -q "$url" -O "/tmp/$filename.tar.gz"; then
tar -xzf "/tmp/$filename.tar.gz" -C "$DEPLOY_DIR"
echo "解压完成: $filename"
else
echo "下载失败: $filename"
fi
done
# 步骤 4:验证部署结果
success=0
for binary in "$DEPLOY_DIR"/*/app; do
if [ -x "$binary" ]; then
if "$binary" --version > /dev/null 2>&1; then
echo "验证通过: $binary"
success=$((success + 1))
fi
fi
done
echo "部署完成,成功验证 $success 个组件"
6 语法速查表
| 结构 | 语法模式 | 适用场景 |
|---|---|---|
if |
if [ 条件 ]; then ... fi |
条件分支、状态检查 |
for-in |
for 变量 in 列表; do ... done |
遍历已知集合、文件列表 |
for-C |
for ((i=0; i<n; i++)); do ... done |
精确计数、序号迭代 |
while |
while [ 条件 ]; do ... done |
条件驱动、未知迭代次数 |
until |
until [ 条件 ]; do ... done |
直到条件成立(反向 while) |
break |
break [n] |
跳出 n 层循环 |
continue |
continue [n] |
跳到下一次循环 |
Shell 脚本的控制结构看似简单,但组合使用能解决绝大多数自动化问题。记住三点原则:if 判断条件要严谨,for 用于遍历已知内容,while 用于条件驱动。勤写脚本,多实践,才能真正掌握。

暂无评论,快来抢沙发吧!