Shell 控制流与函数
更新时间:2026-08-25。本文覆盖 Shell 编程主题的第②族:控制流、函数与脚本健壮性,约 14 个语法点/命令。
一、条件判断 if
bash
if [ -f /etc/os-release ]; then
echo "文件存在"
elif [ -d /etc ]; then
echo "目录存在"
else
echo "都不存在"
fitest 表达式速查:
| 类别 | 表达式 |
|---|---|
| 文件 | -e 存在、-f 常规文件、-d 目录、-r/-w/-x 可读写执行、-s 非空 |
| 字符串 | [ -z "$s" ] 空、[ -n "$s" ] 非空、[ "$a" = "$b" ] 相等、!= 不等 |
| 数值 | -eq -ne -lt -le -gt -ge |
| 逻辑 | ! 取反、-a 且、-o 或(推荐用 [[ ]] 的 &&/` |
| 双括号 | [[ ... ]] 支持正则、&&/` |
bash
if [[ "$name" == *.log ]]; then # 模式匹配/正则
echo "log 文件"
fi二、多分支 case
bash
case "$1" in
start) echo "启动中" ;;
stop) echo "停止中" ;;
restart) echo "重启" ;;
-h|--help) echo "帮助" ;;
*) echo "未知参数: $1" ;;
esaccase 比一长串 if-elif 更清晰,是命令行工具参数分发的标准写法。
三、循环
3.1 for(最常用)
bash
# 遍历列表
for name in a b c; do echo "$name"; done
# C 风格
for ((i = 0; i < 10; i++)); do echo "$i"; done
# 遍历命令输出
for f in *.txt; do echo "$f"; done
# 遍历文件内容
while IFS= read -r line; do echo "行: $line"; done < file.txt3.2 while / until
bash
n=0
while [ $n -lt 5 ]; do # 条件为真则循环
echo "$n"
((n++))
done
n=0
until [ $n -ge 5 ]; do # 条件为假则循环(与 while 相反)
echo "$n"
((n++))
done3.3 select(菜单)
bash
select opt in 启动 停止 重启 退出; do
case $opt in
启动) echo "start" ;;
退出) break ;;
*) echo "无效选项" ;;
esac
done3.4 循环控制
break:跳出循环continue:跳过本次迭代- 循环与
done < file配合实现逐行处理
四、函数
bash
greet() {
local name=$1 # local:局部变量,避免污染全局
echo "你好, $name"
return 0 # 返回退出码(0-255)
}
greet "极客" # 调用函数要点:
- 函数参数用
$1、$2、$@(与脚本参数一致,但函数内是函数自己的参数)。 local声明局部变量,防止函数间互相污染。return返回退出码,不是返回值;要"返回值"用echo+ 命令替换捕获。- 函数内
$?是函数内最后一条命令的退出码。
函数返回值模式
bash
# 用 echo 返回"字符串值"
get_platform() {
echo "$(uname -s)"
}
plat=$(get_platform) # plat=Linux
# 用退出码返回"成败"
is_root() {
[ "$EUID" -eq 0 ]
}
if is_root; then echo "root"; else echo "非root"; fi五、脚本健壮性陷阱(高频坑)
| 陷阱 | 正确写法 |
|---|---|
| 变量含空格被拆词 | 始终 "$var" 加引号 |
| 未定义变量静默用空 | set -u |
| 命令失败不报错 | set -e |
| 管道中命令失败被吞 | set -o pipefail |
| 文件名含空格/通配符 | 数组 + "${arr[@]}" 而非裸 $arr |
[ 与 [[ 混淆 | 现代 Bash 优先 [[ ]] |
用 rm -rf $dir | 校验 [ -n "$dir" ] 后再删,防 rm -rf / |
| 临时文件冲突 | mktemp 生成唯一临时文件/目录 |
六、完整示例:带状态机的部署脚本骨架
bash
#!/bin/bash
set -euo pipefail
STATE=${1:-start} # 默认 start
case "$STATE" in
start)
echo "启动服务..."
;;
stop)
echo "停止服务..."
;;
status)
if pgrep -f myapp > /dev/null; then
echo "运行中"
else
echo "已停止"
fi
;;
*)
echo "用法: $0 {start|stop|status}" >&2
exit 1
;;
esac七、与主线衔接
case+$1是命令行工具的标准写法,也是 Go 实战入门 里 CLI 工具设计的思想源头。- 循环逐行处理 +
set -o pipefail,是日志批处理脚本的骨架,配合 文本处理命令 使用。 - 进程启停脚本衔接本站 进程与系统命令 的
nohup/kill。
一句话总结
Shell 控制流 = if/case/for/while/select + 函数(local + return 退出码);记住 set -euo pipefail、变量加引号、临时文件用 mktemp 三大铁律,脚本就稳了一半。