Shell 流程控制详解(新手详细版)
本文档面向零基础新手,从“什么是流程控制”讲起,详细说明条件判断(if/case)、循环(for/while/until)、退出码、测试命令、break/continue 等,并配有大量示例。
一、什么是流程控制?
1.1 一句话理解
流程控制就是让脚本根据不同条件执行不同代码,或者重复执行某段代码。
- 条件:如果……就……,否则……(if / case)
- 循环:重复做某件事若干次或直到某条件不满足(for / while / until)
1.2 为什么要学?
- 写脚本时经常要“先判断再执行”(如:文件存在才删除、数字大于 0 才计算)。
- 需要“对一批东西逐个处理”(如:遍历当前目录所有 .txt 文件、读文件每一行)。
- 掌握流程控制后,才能写出有用、可复用的 Shell 脚本。
二、先弄清:命令的“成功”与“失败”(退出码)
2.1 退出码(Exit Status)
每条命令执行完后,会向 Shell 返回一个数字,表示“是否成功”:
- 0:成功
- 非 0(常为 1、2 等):失败或异常
Shell 用变量 $? 保存上一条命令的退出码。
ls /tmp
echo $?
# 若 /tmp 存在且可读,输出 0
ls /不存在的路径
echo $?
# 输出非 0(如 2)
2.2 流程控制如何用退出码
- if 后面跟的“条件”,本质上就是一条或一组命令;若该命令的退出码为 0,条件为“真”,否则为“假”。
- 所以:
if 命令; then ...表示“若命令成功(退出码 0),则执行 then 后面的内容”。
if ls /tmp > /dev/null 2>&1; then
echo "存在 /tmp"
fi
下面要讲的 test、[ ]、[[ ]] 都是“专门用来做条件判断”的命令/语法,它们也会返回 0(真)或 非0(假)。
三、条件判断:if / then / else / elif / fi
3.1 基本语法
if 条件; then
条件为真时执行的命令
fi
- if 和 then 可以写在同一行,中间用 ; 隔开;也可以 then 换行。
- fi 是 if 的结束标记(if 倒着写),不能少。
示例:
if [ -f /etc/passwd ]; then
echo "文件 /etc/passwd 存在"
fi
3.2 带 else(二选一)
if 条件; then
条件为真时执行
else
条件为假时执行
fi
示例:
if [ -d /tmp ]; then
echo "/tmp 是目录"
else
echo "/tmp 不是目录"
fi
3.3 多分支:elif
if 条件1; then
命令1
elif 条件2; then
命令2
elif 条件3; then
命令3
else
以上都不满足时执行
fi
示例:按分数分档
score=85
if [ "$score" -ge 90 ]; then
echo "优秀"
elif [ "$score" -ge 60 ]; then
echo "及格"
else
echo "不及格"
fi
四、条件怎么写:test 与 [ ]、[[ ]]
4.1 test 与 [ ] 是一回事
下面两种写法等价:
test -f /etc/passwd
[ -f /etc/passwd ]
- [ ] 是命令名,左右必须有空格:
[ 条件 ],不能写成[条件]。 - 条件为真时退出码 0,为假时退出码 1。
4.2 文件测试(常用)
| 写法 | 含义 |
|---|---|
| -e 路径 | 路径存在(文件或目录均可) |
| -f 路径 | 存在且为普通文件 |
| -d 路径 | 存在且为目录 |
| -r 路径 | 存在且可读 |
| -w 路径 | 存在且可写 |
| -x 路径 | 存在且可执行 |
| -s 路径 | 存在且大小大于 0 |
| -L 路径 | 存在且为符号链接 |
示例:
if [ -f "$HOME/.bashrc" ]; then
echo "存在 .bashrc 文件"
fi
if [ -d /tmp ]; then
echo "/tmp 是目录"
fi
if [ ! -e /不存在的路径 ]; then
echo "路径不存在"
fi
4.3 字符串比较
| 写法 | 含义 |
|---|---|
| -z “串” | 串长度为 0(空) |
| -n “串” | 串长度非 0(非空) |
| “串1” = “串2” | 相等(注意 = 两边有空格) |
| “串1” != “串2” | 不相等 |
重要:变量建议加双引号,避免变量为空或含空格时出错。
name="zhangsan"
if [ -z "$name" ]; then
echo "name 为空"
else
echo "name 是 $name"
fi
if [ "$name" = "zhangsan" ]; then
echo "名字匹配"
fi
4.4 数值比较(用 -eq、-ne 等,不要用 =、!=)
[ ] 里比较数字要用下面这些,不能写 =、<、>(会被当字符串):
| 写法 | 含义 |
|---|---|
| -eq | 等于(equal) |
| -ne | 不等于(not equal) |
| -gt | 大于(greater than) |
| -ge | 大于等于 |
| -lt | 小于(less than) |
| -le | 小于等于 |
count=5
if [ "$count" -gt 0 ]; then
echo "count 大于 0"
fi
if [ "$count" -eq 5 ]; then
echo "count 等于 5"
fi
4.5 逻辑组合:与、或、非
| 写法(在 [ ] 内) | 含义 |
|---|---|
| ! 条件 | 非 |
| 条件1 -a 条件2 | 与(and) |
| 条件1 -o 条件2 | 或(or) |
示例:
if [ -f file.txt -a -r file.txt ]; then
echo "文件存在且可读"
fi
if [ "$a" -gt 0 -o "$b" -lt 10 ]; then
echo "a>0 或 b<10"
fi
4.6 [[ ]](推荐在 Bash 里用)
[[ ]] 是 Bash 的关键字,不是命令,比 [ ] 更不容易出错:
- 里面对 =、!=、<、> 做的是按需比较(数字或字符串)。
- 支持 &&、||、! 直接写,不必用 -a、-o。
- 支持 =~ 做正则匹配。
# 字符串比较
if [[ "$name" == "zhangsan" ]]; then
echo "匹配"
fi
# 正则:变量是否匹配模式
if [[ "$path" =~ ^/home ]]; then
echo "路径在 /home 下"
fi
# 逻辑
if [[ -f file.txt && -r file.txt ]]; then
echo "存在且可读"
fi
# 数值也可用 < >(在 [[ ]] 里)
if [[ $count -gt 0 ]]; then
echo "正数"
fi
建议:写 Bash 脚本时,条件判断优先用 [[ ]],可读性更好。
五、多分支选择:case / esac
当要根据一个变量的多种取值做不同处理时,case 比一串 if-elif 更清晰。
5.1 语法
case 变量 in
模式1)
命令
;;
模式2)
命令
;;
*)
以上都不匹配时执行(类似 else)
;;
esac
- 每个分支以 ;; 结束(不能少)。
- * 表示“其他情况”,可省略。
5.2 示例
fruit="apple"
case "$fruit" in
apple)
echo "苹果"
;;
banana)
echo "香蕉"
;;
orange|lemon)
echo "柑橘类"
;;
*)
echo "未知"
;;
esac
用通配符:
file="readme.txt"
case "$file" in
*.txt)
echo "文本文件"
;;
*.sh)
echo "脚本"
;;
*)
echo "其他"
;;
esac
六、for 循环
6.1 遍历列表:for i in 列表
for 变量 in 项1 项2 项3; do
命令(可用 $变量)
done
示例:
for name in zhangsan lisi wangwu; do
echo "你好, $name"
done
# 遍历当前目录所有 .txt 文件
for f in *.txt; do
echo "文件: $f"
done
# 若没有匹配,会执行一次且变量为字面 *.txt(未展开时)
6.2 遍历命令输出(每行一项)
for i in $(ls); do
echo "$i"
done
# 更稳妥:用 while + read 读行(见后文)
6.3 C 风格:for (( 初始; 条件; 步进 ))
适合按数字循环:
for (( i=1; i<=5; i++ )); do
echo "第 $i 次"
done
# 从 0 到 4
for (( i=0; i<5; i++ )); do
echo "$i"
done
七、while 循环
当条件为真时一直执行循环体;条件在每次循环前判断。
7.1 语法
while 条件; do
命令
done
示例:
# 从 1 数到 5(用变量自增)
i=1
while [ "$i" -le 5 ]; do
echo "$i"
i=$(( i + 1 ))
done
# 读文件每一行(常用)
while IFS= read -r line; do
echo "$line"
done < file.txt
7.2 无限循环与 break
条件永远为真就是“无限循环”,用 break 在某一时刻跳出:
while true; do
echo "输入 quit 退出"
read -r input
if [ "$input" = "quit" ]; then
break
fi
done
八、until 循环
直到条件为真才停止(与 while 相反:while 是“为真则继续”,until 是“为假则继续”)。
i=0
until [ "$i" -ge 5 ]; do
echo "$i"
i=$(( i + 1 ))
done
实际中用 while 更多,until 了解即可。
九、break 与 continue
9.1 break
跳出当前循环(只跳一层),不再执行后续循环。
for i in 1 2 3 4 5; do
if [ "$i" -eq 3 ]; then
break
fi
echo "$i"
done
# 输出:1 2
9.2 continue
跳过本次循环剩余部分,直接进入下一次循环。
for i in 1 2 3 4 5; do
if [ "$i" -eq 3 ]; then
continue
fi
echo "$i"
done
# 输出:1 2 4 5
十、逻辑连接:&& 与 ||(不在 if 里也能用)
- 命令1 && 命令2:命令1 成功(退出码 0)才执行命令2。
- 命令1 || 命令2:命令1 失败(非 0)才执行命令2。
# 只有目录存在才进入并列出
[ -d /tmp ] && cd /tmp && ls
# 若 mkdir 失败则报错退出
mkdir -p backup || { echo "创建目录失败"; exit 1; }
十一、实用示例汇总
11.1 根据文件类型操作
if [ -f "$1" ]; then
echo "$1 是文件"
elif [ -d "$1" ]; then
echo "$1 是目录"
else
echo "$1 不存在或类型未知"
fi
11.2 检查变量是否为空
if [ -z "$USER" ]; then
echo "USER 未设置"
exit 1
fi
11.3 菜单选择(case)
echo "1) 安装 2) 卸载 3) 退出"
read -r choice
case "$choice" in
1) echo "执行安装" ;;
2) echo "执行卸载" ;;
3) exit 0 ;;
*) echo "无效选择" ;;
esac
11.4 批量处理文件
for f in *.txt; do
[ -f "$f" ] || continue
echo "处理: $f"
cp "$f" "$f.bak"
done
11.5 逐行读文件
while IFS= read -r line; do
echo "$line"
done < /etc/hostname
11.6 数字累加(while)
sum=0
i=1
while [ "$i" -le 10 ]; do
sum=$(( sum + i ))
i=$(( i + 1 ))
done
echo "1+2+...+10 = $sum"
十二、常见坑与建议
- [ ] 两边必须有空格:
[ "$a" -eq 1 ],不能["$a"-eq 1]。 - 变量加双引号:
[ "$var" = "x" ],避免空变量或含空格时语法错误。 - 数字比较用 -eq 等:在 [ ] 里用 -eq、-ne、-gt、-lt,不要用 =、(会按字符串比较)。
- then、do、fi、done 单独一行或分号:then/do 可与 if/for/while 同一行用 ; 隔开。
- Bash 里推荐 [[ ]]:支持 &&、||、=~,更清晰。
- case 分支用 ;; 结束:漏写 ;; 会继续执行下一个分支。
十三、语法速查表
| 结构 | 写法要点 |
|---|---|
| if | if 条件; then ... elif ... else ... fi |
| case | case $var in 模式) ... ;; esac |
| for 列表 | for i in 1 2 3; do ... done |
| for C 风格 | for (( i=0; i<5; i++ )); do ... done |
| while | while 条件; do ... done |
| until | until 条件; do ... done |
| break / continue | 在循环内使用 |
| 条件([ ] 或 [[ ]]) | 示例 |
|---|---|
| 文件存在 | [ -f file ]、[ -d dir ] |
| 字符串空/非空 | [ -z "$s" ]、[ -n "$s" ] |
| 字符串相等 | [ "$a" = "$b" ]、[[ "$a" == "$b" ]] |
| 数值比较 | [ "$n" -eq 1 ]、[ "$n" -gt 0 ] |
| 逻辑 | [[ -f f && -r f ]]、[ ! -e f ] |
十四、小结
- 流程控制包括:条件(if、case)和循环(for、while、until);条件基于命令退出码和 test/[ ]/[[ ]]。
- 条件:文件测试(-f、-d、-r 等)、字符串(-z、-n、=、!=)、数值(-eq、-gt 等);Bash 下推荐 [[ ]]。
- 循环:for in 遍历列表,for (( )) 数字循环,while 条件为真则继续,until 条件为真则停;break/ continue 控制跳出或跳过。
- case 适合多分支选择;&&、|| 可简化“成功才执行”“失败才执行”的写法。
建议在脚本里多写几遍 if、for、while、case,再结合 Linux简介.md 里“简单脚本示例”和 shell的grep命令.md 等,把流程控制和命令组合起来用,就能写出实用的 Shell 脚本。
文档以 Bash 为准;sh 或其它 Shell 在 [[ ]]、(( )) 等细节上可能略有差异。