Shell流程控制

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
  • ifthen 可以写在同一行,中间用 ; 隔开;也可以 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"

十二、常见坑与建议

  1. [ ] 两边必须有空格[ "$a" -eq 1 ],不能 ["$a"-eq 1]
  2. 变量加双引号[ "$var" = "x" ],避免空变量或含空格时语法错误。
  3. 数字比较用 -eq 等:在 [ ] 里用 -eq、-ne、-gt、-lt,不要用 =、(会按字符串比较)。
  4. then、do、fi、done 单独一行或分号:then/do 可与 if/for/while 同一行用 ; 隔开。
  5. Bash 里推荐 [[ ]]:支持 &&||=~,更清晰。
  6. 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 在 [[ ]]、(( )) 等细节上可能略有差异。

发表评论