高级 Bash 脚本编程指南 --- 实战教程
实验环境 : 华为云 ECS ecs-76fc 集群 (4×8vCPU/16GiB, Ubuntu 24.04)
Bash 版本 : GNU bash 5.2.21(1)-release
创建日期 : 2026-06-16
知识点: Bash入门、特殊字符、变量参数、运算符、流程控制、函数
目录
- [实验1: Bash 介绍与入门](#实验1: Bash 介绍与入门)
- [挑战: 简单的热身](#挑战: 简单的热身)
- [实验2: Bash 特殊字符(上)](#实验2: Bash 特殊字符(上))
- [实验3: Bash 特殊字符(下)](#实验3: Bash 特殊字符(下))
- [实验4: 变量和参数](#实验4: 变量和参数)
- [实验5: 基本运算符](#实验5: 基本运算符)
- [挑战: 矩形的面积和周长](#挑战: 矩形的面积和周长)
- [实验6: 流程控制](#实验6: 流程控制)
- [挑战: 最大值](#挑战: 最大值)
- [挑战: 偶数之和](#挑战: 偶数之和)
- [实验7: 函数](#实验7: 函数)
实验1: Bash 介绍与入门
1.1 什么是 Bash
Bash (Bourne Again SHell) 是 GNU 项目的 Shell 程序,是大多数 Linux 发行版的默认 Shell。它是 Bourne Shell (sh) 的增强替代品,兼容 sh 语法同时添加了大量新特性。
+------------------+ +------------------+
| 用户终端 | | Shell 脚本 |
| (交互式) | | (批处理) |
+--------+---------+ +--------+---------+
| |
v v
+------------------------------------------+
| Bash 解释器 |
| - 命令解析 - 变量替换 - 通配符展开 |
| - 流程控制 - 函数调用 - 信号处理 |
+------------------------------------------+
|
v
+------------------------------------------+
| Linux 内核 / 系统调用 |
+------------------------------------------+
1.2 Bash 版本与 Shell 类型
bash
# 查看 Bash 版本
which bash
# /usr/bin/bash
bash --version
# GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)
# 查看系统支持的 Shell
cat /etc/shells
# /etc/shells: valid login shells
# /bin/sh
# /usr/bin/sh
# /bin/bash
# /usr/bin/bash
# /bin/rbash
# /usr/bin/rbash
# /usr/bin/dash
# /usr/bin/screen
# /usr/bin/tmux
# 查看当前 Shell
echo $SHELL
# /bin/bash
Shell 类型对比:
| Shell | 路径 | 说明 |
|---|---|---|
| sh (Bourne Shell) | /bin/sh | 最早的 Unix Shell,功能简单 |
| bash (Bourne Again Shell) | /bin/bash | 最常用,sh 的增强版 |
| dash (Debian Almquist Shell) | /usr/bin/dash | 轻量级,POSIX 兼容,Ubuntu 默认 /bin/sh |
| rbash (Restricted Bash) | /bin/rbash | 受限 Bash,禁止 cd/重定向等 |
| zsh | /usr/bin/zsh | 功能丰富,macOS 默认 |
| fish | /usr/bin/fish | 用户友好,语法不兼容 bash |
1.3 Hello World --- 第一个脚本
bash
#!/bin/bash
# 这是一个简单的 Bash 脚本
echo "Hello, World!"
echo "当前用户: $(whoami)"
echo "当前日期: $(date)"
echo "当前目录: $(pwd)"
执行输出:
Hello, World!
当前用户: root
当前日期: Tue Jun 16 08:15:04 PM CST 2026
当前目录: /root
1.4 脚本执行的三种方式
| 方式 | 命令 | 特点 |
|---|---|---|
| bash 执行 | bash script.sh |
子 Shell 执行,需要 bash 环境 |
| 直接执行 | ./script.sh |
需要 chmod +x 权限和 shebang 行 |
| source 执行 | source script.sh 或 . script.sh |
当前 Shell 执行,变量保留 |
⚠️ 关键区别 :
source在当前 Shell 中执行,脚本中的变量修改会影响当前环境。bash和./在子 Shell 中执行,变量修改不会影响当前环境。
挑战: 简单的热身
系统信息报告脚本
bash
#!/bin/bash
# 系统信息脚本
echo "===== 系统信息报告 ====="
echo "日期: $(date)"
echo "用户: $(whoami)"
echo "主机: $(hostname)"
echo "系统: $(uname -s -r)"
echo "运行时间: $(uptime -p)"
echo "内存使用:"
free -h | head -2
echo "磁盘使用:"
df -h / | head -2
echo "===== 报告结束 ====="
执行输出:
===== 系统信息报告 =====
日期: Tue Jun 16 08:17:05 PM CST 2026
用户: root
主机: ecs-76fc-0001
系统: Linux 6.8.0-106-generic
运行时间: up 30 minutes
内存使用:
total used free shared buff/cache available
Mem: 14Gi 532Mi 13Gi 2.5Mi 1.1Gi 14Gi
磁盘使用:
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 3.5G 35G 10% /
===== 报告结束 =====
倒计时脚本
bash
#!/bin/bash
for i in 5 4 3 2 1; do
echo -n "$i... "
sleep 0.2
done
echo "Go!"
输出 : 5... 4... 3... 2... 1... Go!
实验2: Bash 特殊字符(上)
2.1 注释符 #
bash
echo "这是可见文本" # 这是注释
echo "引号内 # 不是注释:"
echo "这个 # 不是注释"
输出:
这是可见文本
引号内 # 不是注释:
这个 # 不是注释
💡
#在行首或词首是注释,但在引号内、${#var}(字符串长度)、$#(参数个数) 中不是注释。
2.2 命令分隔符 ;
bash
echo "第一句"; echo "第二句"; echo "第三句"
输出:
第一句
第二句
第三句
;允许在同一行写多个命令,等价于换行。
2.3 双引号 vs 单引号
bash
NAME="Bash"
echo "双引号: Hello, $NAME" # 变量替换
echo '单引号: Hello, $NAME' # 原样输出
输出:
双引号: Hello, Bash
单引号: Hello, $NAME
引号对比表:
| 引号类型 | 示例 | 变量替换 | 命令替换 | 特殊字符 |
|---|---|---|---|---|
双引号 "..." |
"$HOME" |
✅ 替换 | ✅ 替换 | $ \ !` 需转义 |
单引号 '...' |
'$HOME' |
❌ 不替换 | ❌ 不替换 | 全部原样 |
美元引号 $'...' |
$'\t' |
❌ 不替换 | ❌ 不替换 | 转义序列生效 |
反引号 ... |
pwd |
--- | ✅ 替换 | 难嵌套,推荐 $() |
2.4 命令替换 $()
bash
RESULT1=$(pwd)
RESULT2=$(date +%Y-%m-%d)
echo "当前目录 -> $RESULT1"
echo "今天日期 -> $RESULT2"
输出:
当前目录 -> /root
今天日期 -> 2026-06-16
💡
$(command)优于反引号command:支持嵌套、可读性好。
2.5 转义符 \
bash
echo "原样输出 \$NAME"
echo "包含双引号: \"hello\""
输出:
原样输出 $NAME
包含双引号: "hello"
2.6 冒号 : (空命令)
bash
: 这是一个空操作
echo "冒号返回值: $?"
: ${VAR:=default_value}
echo "VAR = $VAR"
输出:
冒号返回值: 0
VAR = default_value
💡
:永远返回 0 (成功)。常用于:① 占位 ② 变量默认值${VAR:=default}③ 无限循环while :; do ...; done
2.7 通配符 ? 和 *
bash
touch /tmp/test1.txt /tmp/test2.txt /tmp/test3.log
echo "? 匹配单字符:"
ls /tmp/test?.txt
# /tmp/test1.txt
# /tmp/test2.txt
echo "* 匹配任意:"
ls /tmp/test*
# /tmp/test1.txt /tmp/test2.txt /tmp/test3.log
通配符对比:
| 通配符 | 含义 | 示例 | 匹配 |
|---|---|---|---|
? |
任意单个字符 | test?.txt |
test1.txt, test2.txt |
* |
任意0或多个字符 | test* |
test1.txt, test2.txt, test3.log |
[...] |
字符集 | test[12].txt |
test1.txt, test2.txt |
[!...] |
排除字符集 | test[!3]* |
test1.txt, test2.txt |
{a,b} |
花括号展开 | {A,B,C} |
A B C |
实验3: Bash 特殊字符(下)
3.1 管道 | 和进程替换 <()
bash
# 管道: 前一命令的 stdout 作为后一命令的 stdin
echo "hello world" | tr a-z A-Z
# HELLO WORLD
# 进程替换: 将命令输出模拟为文件
echo <(echo "左边") <(echo "右边")
# /dev/fd/63 /dev/fd/62
cat <(echo "进程替换输出")
# 进程替换输出
管道 vs 进程替换:
| 特性 | 管道 | | 进程替换 <() |
|------|----------|---------------|
| 数据流 | stdout → stdin | 命令输出 → 临时文件 |
| 适用 | 串联命令 | 需要文件参数的命令 |
| 并行 | 两个命令同时运行 | 同时运行 |
| 示例 | cmd1 \| cmd2 | diff <(cmd1) <(cmd2) |
3.2 重定向
bash
# stdout 重定向到文件
echo " stdout到文件" > /tmp/redirect_test.txt
cat /tmp/redirect_test.txt
# stdout到文件
# 追加
echo " 追加" >> /tmp/redirect_test.txt
cat /tmp/redirect_test.txt
# stdout到文件
# 追加
# stderr 丢弃
ls /nonexist 2>/dev/null
echo "错误已隐藏, 返回值=$?"
# 错误已隐藏, 返回值=2
# stdin 来自文件
echo -e "line1\nline2\nline3" > /tmp/input.txt
cat < /tmp/input.txt
# line1
# line2
# line3
重定向符号汇总:
| 符号 | 含义 | 示例 |
|---|---|---|
> |
stdout 覆盖写入 | echo "hi" > file |
>> |
stdout 追加写入 | echo "hi" >> file |
2> |
stderr 写入 | cmd 2> err.log |
2>/dev/null |
stderr 丢弃 | cmd 2>/dev/null |
&> |
stdout+stderr 写入 | cmd &> all.log |
< |
stdin 来自文件 | cmd < input.txt |
<< |
Here Document | cmd << EOF |
<<< |
Here String | cmd <<< "text" |
2>&1 |
stderr 合并到 stdout | cmd > out.log 2>&1 |
3.3 子Shell ()
bash
(echo "子shell PID: $$"; echo "子shell PPID: $PPID")
echo "当前shell PID: $$"
输出:
子shell PID: 955
子shell PPID: 1
当前shell PID: 955
⚠️
()内的变量修改不影响外部。常用于临时修改环境:(cd /tmp; ls)
3.4 花括号扩展 {}
bash
echo {A,B,C} # A B C
echo {1..5} # 1 2 3 4 5
echo file{1..3}.txt # file1.txt file2.txt file3.txt
mkdir -p /tmp/brace{01..03}
ls -d /tmp/brace*
# /tmp/brace01 /tmp/brace02 /tmp/brace03
3.5 方括号 [] 和 [[]]
bash
# test 命令 []
[ 5 -gt 3 ] && echo "5 > 3 为真"
# 5 > 3 为真
# 增强测试 [[]]
[[ "hello" == h* ]] && echo "模式匹配成功"
# 模式匹配成功
[] vs [[]] 对比:
| 特性 | [] (POSIX) |
[[]] (Bash) |
|---|---|---|
| 兼容性 | 所有 Shell | 仅 Bash/Ksh/Zsh |
| 逻辑运算 | -a / -o |
&& / ` |
| 模式匹配 | 不支持 | == + 通配符 |
| 正则匹配 | 不支持 | =~ |
| 空格安全 | 需引号 | 更安全 |
| 推荐 | 跨Shell脚本 | Bash 专用脚本 |
3.6 美元符号 $ 特殊变量
| 变量 | 含义 | 示例 |
|---|---|---|
$0 |
脚本名 | /tmp/script.sh |
$1~$9 |
位置参数 | 第1~9个参数 |
$# |
参数个数 | 3 |
$@ |
所有参数(独立) | "a" "b" "c" |
$* |
所有参数(整体) | "a b c" |
$? |
上一命令返回值 | 0 (成功) |
$$ |
当前进程 PID | 14521 |
$! |
后台最后进程 PID | 14525 |
$_ |
上一命令最后参数 | --- |
实验4: 变量和参数
4.1 变量赋值与引用
bash
NAME="Linux"
VERSION=5
echo "NAME=$NAME"
echo "VERSION=$VERSION"
echo "花括号引用: ${NAME} kernel ${VERSION}.x"
# 只读变量
readonly PI=3.14159
echo "PI=$PI"
# PI=3 # bash: PI: readonly variable
输出:
NAME=Linux
VERSION=5
花括号引用: Linux kernel 5.x
PI=3.14159
⚠️ 赋值等号两边不能有空格 :
NAME="Linux"✓ |NAME = "Linux"✗ (会被解释为命令)
4.2 环境变量
bash
echo "HOME=$HOME" # /root
echo "USER=$USER" # root
echo "PATH=$PATH" # /usr/local/sbin:/usr/local/bin:...
echo "SHELL=$SHELL" # /bin/bash
echo "LANG=$LANG" # en_US.UTF-8
# 自定义环境变量
export MY_ENV="hello_from_net01"
echo "MY_ENV=$MY_ENV"
局部变量 vs 环境变量:
| 类型 | 定义方式 | 作用域 | 子进程可见 |
|---|---|---|---|
| 局部变量 | VAR=value |
当前 Shell | ❌ |
| 环境变量 | export VAR=value |
当前 Shell 及子进程 | ✅ |
4.3 位置参数
bash
#!/bin/bash
echo "脚本名 $0 = $0"
echo "参数1 $1 = $1"
echo "参数2 $2 = $2"
echo "参数3 $3 = $3"
echo "参数个数 $# = $#"
echo "所有参数 $@ = $@"
echo "所有参数 $* = $*"
echo "--- $@ vs $* 区别 ---"
echo "\$@ 逐个处理:"
for arg in "$@"; do echo " arg: $arg"; done
echo "\$* 整体处理:"
for arg in "$*"; do echo " arg: $arg"; done
执行 : bash params.sh hello world "foo bar" baz
输出:
脚本名 /tmp/params.sh = /tmp/params.sh
参数1 hello = hello
参数2 world = world
参数3 foo bar = foo bar
参数个数 4 = 4
所有参数 hello world foo bar baz = hello world foo bar baz
所有参数 hello world foo bar baz = hello world foo bar baz
--- $@ vs $* 区别 ---
$@ 逐个处理:
arg: hello
arg: world
arg: foo bar
arg: baz
$* 整体处理:
arg: hello world foo bar baz
💡
"$@"vs"$*": 双引号下$@保持每个参数独立,$*合并为一个字符串。编程时始终用"$@"。
4.4 变量默认值操作
bash
unset UNSET_VAR
echo "${UNSET_VAR:-默认值}" # 默认值 (变量不变)
echo "UNSET_VAR仍为空: [$UNSET_VAR]"
echo "${UNSET_VAR:=赋默认值}" # 赋默认值 (变量被赋值)
echo "UNSET_VAR现在: [$UNSET_VAR]"
unset UNSET_VAR
echo "${UNSET_VAR:+替代值}" # 有值则替代 (变量不变)
EXISTING="yes"
echo "${EXISTING:+替代值}" # 替代值
输出:
默认值
UNSET_VAR仍为空: []
赋默认值
UNSET_VAR现在: [赋默认值]
替代值
变量操作速查表:
| 语法 | 含义 | 变量是否改变 |
|---|---|---|
${var:-default} |
变量空则返回默认值 | ❌ 不改变 |
${var:=default} |
变量空则赋默认值并返回 | ✅ 改变 |
${var:+alternate} |
变量非空则返回替代值 | ❌ 不改变 |
${var:?error_msg} |
变量空则报错退出 | --- |
${#var} |
字符串长度 | ❌ |
${var:offset:length} |
子串截取 | ❌ |
${var#pattern} |
从头删最短匹配 | ❌ |
${var##pattern} |
从头删最长匹配 | ❌ |
${var%pattern} |
从尾删最短匹配 | ❌ |
${var%%pattern} |
从尾删最长匹配 | ❌ |
${var/old/new} |
替换第一个 | ❌ |
${var//old/new} |
替换全部 | ❌ |
实验5: 基本运算符
5.1 算术运算符
bash
a=10
b=3
echo "a=$a, b=$b"
echo "加法 $((a+b)) = $((a+b))" # 13
echo "减法 $((a-b)) = $((a-b))" # 7
echo "乘法 $((a*b)) = $((a*b))" # 30
echo "除法 $((a/b)) = $((a/b))" # 3 (整除)
echo "取余 $((a%b)) = $((a%b))" # 1
echo "幂运算 $((a**2)) = $((a**2))" # 100
输出:
a=10, b=3
加法 13 = 13
减法 7 = 7
乘法 30 = 30
除法 3 = 3
取余 1 = 1
幂运算 100 = 100
⚠️ Bash 算术只支持整数 。浮点运算需要
bc或awk。
算术运算方式对比:
| 方式 | 示例 | 整数 | 浮点 | 推荐 |
|---|---|---|---|---|
$((...)) |
$((5+3)) |
✅ | ❌ | ⭐ 首选 |
let |
let x=5+3 |
✅ | ❌ | 简洁赋值 |
$[...] |
$[5+3] |
✅ | ❌ | 旧语法,不推荐 |
expr |
expr 5 + 3 |
✅ | ❌ | 需要空格,慢 |
bc |
`echo "5.5+3.2" | bc` | ✅ | ✅ |
awk |
awk 'BEGIN{print 5.5+3.2}' |
✅ | ✅ | 浮点+格式化 |
5.2 比较运算符
数值比较:
bash
[[ 5 -eq 5 ]] && echo "5 -eq 5: 真" # 等于
[[ 5 -ne 3 ]] && echo "5 -ne 3: 真" # 不等于
[[ 5 -gt 3 ]] && echo "5 -gt 3: 真" # 大于
[[ 5 -lt 3 ]] || echo "5 -lt 3: 假" # 小于
[[ 5 -ge 5 ]] && echo "5 -ge 5: 真" # 大于等于
[[ 5 -le 3 ]] || echo "5 -le 3: 假" # 小于等于
字符串比较:
bash
[[ "hello" == "hello" ]] && echo "hello == hello: 真"
[[ "hello" != "world" ]] && echo "hello != world: 真"
[[ -z "" ]] && echo "空字符串 -z: 真"
[[ -n "abc" ]] && echo "非空字符串 -n: 真"
数值 vs 字符串比较对比:
| 操作 | 数值 | 字符串 |
|---|---|---|
| 等于 | -eq |
== 或 = |
| 不等于 | -ne |
!= |
| 大于 | -gt |
> (需转义或[[]]) |
| 小于 | -lt |
< (需转义或[[]]) |
| 大于等于 | -ge |
--- |
| 小于等于 | -le |
--- |
| 空判断 | --- | -z |
| 非空判断 | --- | -n |
5.3 逻辑运算符
bash
[[ 1 -eq 1 && 2 -eq 2 ]] && echo "AND: 真"
[[ 1 -eq 1 || 2 -eq 3 ]] && echo "OR: 真"
[[ ! 0 -eq 1 ]] && echo "NOT: 真"
5.4 文件测试运算符
bash
touch /tmp/testfile
mkdir -p /tmp/testdir
[[ -e /tmp/testfile ]] && echo "-e 文件存在: 真"
[[ -f /tmp/testfile ]] && echo "-f 普通文件: 真"
[[ -r /tmp/testfile ]] && echo "-r 可读: 真"
[[ -w /tmp/testfile ]] && echo "-w 可写: 真"
[[ -s /tmp/testfile ]] || echo "-s 非空: 假(空文件)"
[[ -d /tmp/testdir ]] && echo "-d 目录: 真"
[[ -x /tmp/testfile ]] && echo "-x 可执行: 真" || echo "-x 可执行: 假"
文件测试运算符速查:
| 运算符 | 含义 | 运算符 | 含义 |
|---|---|---|---|
-e |
存在 | -s |
非空(>0字节) |
-f |
普通文件 | -d |
目录 |
-r |
可读 | -w |
可写 |
-x |
可执行 | -L |
符号链接 |
-b |
块设备 | -c |
字符设备 |
-S |
Socket | -p |
命名管道 |
-nt |
比...新 | -ot |
比...旧 |
-ef |
相同inode | --- | --- |
挑战: 矩形的面积和周长
方法1: 位置参数 (整数)
bash
#!/bin/bash
LENGTH=$1
WIDTH=$2
if [[ -z "$LENGTH" || -z "$WIDTH" ]]; then
echo "用法: $0 <长> <宽>"
exit 1
fi
AREA=$((LENGTH * WIDTH))
PERIMETER=$((2 * (LENGTH + WIDTH)))
echo "矩形 (长=$LENGTH, 宽=$WIDTH):"
echo " 面积 = $AREA"
echo " 周长 = $PERIMETER"
执行 : bash rect1.sh 8 5
矩形 (长=8, 宽=5):
面积 = 40
周长 = 26
方法2: bc 浮点运算
bash
#!/bin/bash
L=7.5
W=3.2
AREA=$(echo "$L * $W" | bc)
PERIMETER=$(echo "2 * ($L + $W)" | bc)
echo "矩形 (长=$L, 宽=$W):"
echo " 面积 = $AREA"
echo " 周长 = $PERIMETER"
执行输出:
矩形 (长=7.5, 宽=3.2):
面积 = 24.0
周长 = 21.4
方法3: awk 浮点运算 (推荐)
bash
awk 'BEGIN{L=7.5; W=3.2; printf "面积 = %.2f\n周长 = %.2f\n", L*W, 2*(L+W)}'
# 面积 = 24.00
# 周长 = 21.40
💡
awk浮点运算最方便,支持printf格式化,一行搞定。
实验6: 流程控制
6.1 if/elif/else
bash
score=85
if [[ $score -ge 90 ]]; then
echo "成绩 $score: 优秀"
elif [[ $score -ge 80 ]]; then
echo "成绩 $score: 良好"
elif [[ $score -ge 60 ]]; then
echo "成绩 $score: 及格"
else
echo "成绩 $score: 不及格"
fi
输出 : 成绩 85: 良好
6.2 for 循环
bash
# 遍历列表
for fruit in apple banana cherry; do
echo " 我喜欢 $fruit"
done
# C风格 for
for ((i=1; i<=5; i++)); do
echo -n "$i "
done
echo ""
# 遍历文件
for f in /tmp/test1.txt /tmp/test2.txt /tmp/test3.log; do
echo " 文件: $(basename $f)"
done
输出:
我喜欢 apple
我喜欢 banana
我喜欢 cherry
1 2 3 4 5
文件: test1.txt
文件: test2.txt
文件: test3.log
6.3 while / until 循环
bash
# while: 条件为真时执行
count=1
while [[ $count -le 5 ]]; do
echo -n "$count "
((count++))
done
echo ""
# 输出: 1 2 3 4 5
# until: 条件为假时执行 (与 while 相反)
n=1
until [[ $n -gt 5 ]]; do
echo -n "$n "
((n++))
done
echo ""
# 输出: 1 2 3 4 5
while vs until:
| 语句 | 执行条件 | 等价 |
|---|---|---|
while condition |
条件为真时执行 | while [[ true ]] |
until condition |
条件为假时执行 | while [[ ! condition ]] |
6.4 case 语句
bash
for day in 1 3 5 6 7 9; do
case $day in
1) echo "星期一 Monday";;
3) echo "星期三 Wednesday";;
5) echo "星期五 Friday";;
6|7) echo "周末 Weekend!";; # | 表示或
*) echo "无效输入: $day";; # * 表示默认
esac
done
输出:
星期一 Monday
星期三 Wednesday
星期五 Friday
周末 Weekend!
周末 Weekend!
无效输入: 9
💡 每个分支以
;;结尾(类似 C 的break),;;&为贯穿(fall-through)。
6.5 select 菜单
bash
PS3="请选择编号: "
select item in "查看时间" "查看用户" "退出"; do
case $item in
"查看时间") date;;
"查看用户") whoami;;
"退出") echo "再见!"; break;;
*) echo "无效选择";;
esac
break
done
输出:
1) 查看时间
2) 查看用户
3) 退出
请选择编号: Tue Jun 16 08:19:18 PM CST 2026
6.6 break 和 continue
bash
# break: 跳出整个循环
for i in 1 2 3 4 5; do
if [[ $i -eq 3 ]]; then break; fi
echo -n "$i "
done
echo ""
# 输出: 1 2
# continue: 跳过本次,继续下一次
for i in 1 2 3 4 5; do
if [[ $i -eq 3 ]]; then continue; fi
echo -n "$i "
done
echo ""
# 输出: 1 2 4 5
循环控制对比:
| 关键字 | 作用 | 类比 C |
|---|---|---|
break |
跳出整个循环 | break |
break n |
跳出 n 层循环 | --- |
continue |
跳过本次迭代 | continue |
continue n |
跳到第 n 层循环的下一次 | --- |
挑战: 最大值
方法1: 遍历比较
bash
find_max() {
local max=$1
shift
for num in "$@"; do
if [[ $num -gt $max ]]; then
max=$num
fi
done
echo $max
}
numbers=(34 12 89 5 67 23 91 45)
echo "数组: ${numbers[*]}"
max_val=$(find_max "${numbers[@]}")
echo "最大值: $max_val"
输出:
数组: 34 12 89 5 67 23 91 45
最大值: 91
方法2: 排序取最大
bash
sorted=$(printf '%s\n' "${numbers[@]}" | sort -n)
max_val=$(printf '%s\n' "${numbers[@]}" | sort -n | tail -1)
echo "排序后: $(echo $sorted)"
echo "最大值: $max_val"
输出:
排序后: 5 12 23 34 45 67 89 91
最大值: 91
方法3: 递归求最大值
bash
rec_max() {
if [[ $# -eq 1 ]]; then
echo $1
return
fi
local first=$1
shift
local rest_max=$(rec_max "$@")
if [[ $first -gt $rest_max ]]; then
echo $first
else
echo $rest_max
fi
}
max_val=$(rec_max "${numbers[@]}")
echo "递归求最大值: $max_val"
输出 : 递归求最大值: 91
挑战: 偶数之和
方法1: 循环 + 取余判断
bash
sum_even=0
for num in "${numbers[@]}"; do
if [[ $((num % 2)) -eq 0 ]]; then
sum_even=$((sum_even + num))
echo " 偶数: $num"
fi
done
echo "偶数之和: $sum_even"
输出:
偶数: 34
偶数: 12
偶数之和: 46
方法2: 数学公式验证 (1-100偶数和)
bash
total=0
for i in $(seq 2 2 100); do
total=$((total + i))
done
echo "1到100偶数之和: $total"
echo "验证: 2+4+...+100 = (2+100)*50/2 = $(( (2+100)*50/2 ))"
输出:
1到100偶数之和: 2550
验证: 2+4+...+100 = (2+100)*50/2 = 2550
方法3: 从文件中筛选偶数
bash
# 准备数据文件
echo -e "15\n28\n33\n42\n57\n64\n71\n80\n99\n100" > /tmp/numbers.txt
sum=0
while read num; do
if [[ $((num % 2)) -eq 0 ]]; then
echo " 偶数: $num"
sum=$((sum + num))
fi
done < /tmp/numbers.txt
echo "偶数之和: $sum"
输出:
偶数: 28
偶数: 42
偶数: 64
偶数: 80
偶数: 100
偶数之和: 314
方法4: awk 一行搞定
bash
awk '{if($1%2==0) {sum+=$1; printf " 偶数: %d\n", $1}} END{printf "偶数之和: %d\n", sum}' /tmp/numbers.txt
输出:
偶数: 28
偶数: 42
偶数: 64
偶数: 80
偶数: 100
偶数之和: 314
实验7: 函数
7.1 函数定义与调用
bash
greet() {
echo "Hello, $1! 欢迎来到 $2"
}
greet "Alice" "Linux世界"
greet "Bob" "Bash编程"
输出:
Hello, Alice! 欢迎来到 Linux世界
Hello, Bob! 欢迎来到 Bash编程
7.2 函数返回值 (echo)
bash
add() {
local sum=$(( $1 + $2 ))
echo $sum # 通过 echo 返回值
}
result=$(add 10 20)
echo "10 + 20 = $result"
输出 : 10 + 20 = 30
💡 Bash 函数通过
echo返回数据(捕获 stdout),return只能返回 0-255 的退出码。
7.3 return 退出码
bash
is_even() {
if [[ $(($1 % 2)) -eq 0 ]]; then
return 0 # 成功
else
return 1 # 失败
fi
}
for n in 2 3 4 7; do
if is_even $n; then
echo "$n 是偶数"
else
echo "$n 是奇数"
fi
done
输出:
2 是偶数
3 是奇数
4 是偶数
7 是奇数
函数返回值方式对比:
| 方式 | 范围 | 用途 | 获取方式 |
|---|---|---|---|
echo |
任意字符串 | 返回数据 | $(func) |
return |
0-255 整数 | 返回状态码 | $? |
| 全局变量 | 任意 | 共享数据 | 直接引用 |
7.4 局部变量 local
bash
local_test() {
local var="我是局部变量"
echo "函数内: var=$var"
}
local_test
echo "函数外: var=$var (空,因为local作用域)"
输出:
函数内: var=我是局部变量
函数外: var= (空,因为local作用域)
⚠️ 始终用
local声明函数内变量,避免污染全局作用域!
7.5 递归函数
bash
factorial() {
if [[ $1 -le 1 ]]; then
echo 1
else
local prev=$(factorial $(( $1 - 1 )))
echo $(( $1 * prev ))
fi
}
for n in 1 5 8 10; do
result=$(factorial $n)
echo "$n! = $result"
done
输出:
1! = 1
5! = 120
8! = 40320
10! = 3628800
7.6 函数数组参数
bash
sum_array() {
local sum=0
for num in "$@"; do
sum=$((sum + num))
done
echo $sum
}
arr=(10 20 30 40 50)
total=$(sum_array "${arr[@]}")
echo "数组 ${arr[*]} 的总和 = $total"
输出 : 数组 10 20 30 40 50 的总和 = 150
7.7 高级: 回调函数
bash
apply() {
local func=$1
shift
$func "$@"
}
double() { echo $(( $1 * 2 )); }
square() { echo $(( $1 * $1 )); }
for val in 3 5 7; do
d=$(apply double $val)
s=$(apply square $val)
echo "$val: 双倍=$d, 平方=$s"
done
输出:
3: 双倍=6, 平方=9
5: 双倍=10, 平方=25
7: 双倍=14, 平方=49
附录A: Bash 特殊字符速查表
| 字符 | 名称 | 用途 | 示例 |
|---|---|---|---|
# |
井号 | 注释 / 参数个数 | # comment / $# |
; |
分号 | 命令分隔 | cmd1; cmd2 |
;; |
双分号 | case 终止 | pattern) cmd;; |
. |
点号 | source / 当前目录 | . script / ./run |
: |
冒号 | 空命令 (返回0) | : ${VAR:=default} |
" |
双引号 | 弱引用 (变量替换) | "$HOME" |
' |
单引号 | 强引用 (原样输出) | '$HOME' |
| ``` | 反引号 | 命令替换 (旧) | pwd |
$() |
美元括号 | 命令替换 (新) | $(pwd) |
$((...)) |
双括号 | 算术运算 | $((5+3)) |
\ |
反斜杠 | 转义 | \$HOME |
/ |
斜杠 | 路径分隔 / 除法 | /tmp/file |
| ` | ` | 管道 | 前一命令→后一命令 |
& |
和号 | 后台运行 / 逻辑与 | cmd & / && |
> |
大于号 | 输出重定向 | cmd > file |
< |
小于号 | 输入重定向 | cmd < file |
>> |
双大于 | 追加重定向 | cmd >> file |
<< |
双小于 | Here Document | cmd << EOF |
() |
小括号 | 子Shell | (cd /tmp; ls) |
{} |
花括号 | 花括号扩展 / 代码块 | {A,B,C} |
[] |
方括号 | test 命令 | [ -f file ] |
[[]] |
双方括号 | 增强测试 | [[ $a == b* ]] |
? |
问号 | 通配单个字符 | file?.txt |
* |
星号 | 通配任意字符 | file* |
! |
叹号 | 取反 / 历史扩展 | ! true / !! |
~ |
波浪号 | 家目录 | ~/Documents |
附录B: Bash 运算符速查表
算术运算符
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
+ |
加 | $((5+3)) |
8 |
- |
减 | $((5-3)) |
2 |
* |
乘 | $((5*3)) |
15 |
/ |
除(整除) | $((7/3)) |
2 |
% |
取余 | $((7%3)) |
1 |
** |
幂 | $((2**10)) |
1024 |
++ |
自增 | $((a++)) |
a先返后加 |
-- |
自减 | $((a--)) |
a先返后减 |
比较运算符
| 数值 | 字符串 | 含义 |
|---|---|---|
-eq |
== |
等于 |
-ne |
!= |
不等于 |
-gt |
> |
大于 |
-lt |
< |
小于 |
-ge |
>= |
大于等于 |
-le |
<= |
小于等于 |
逻辑运算符
| 符号 | 含义 | [[]]内 |
[]内 |
|---|---|---|---|
| AND | 与 | && |
-a |
| OR | 或 | ` | |
| NOT | 非 | ! |
! |
📌 总结 : Bash 脚本编程是 Linux 运维和开发的核心技能。掌握特殊字符、变量参数、运算符、流程控制和函数,就能编写出功能强大的自动化脚本。
关键原则 : ① 始终用
"$@"处理参数 ② 函数内用local声明变量 ③ 优先用$((...))做算术 ④ 用[[]]替代[]做测试 ⑤echo返回数据,return返回状态码。