高级 Bash 脚本编程指南 — 实战教程

高级 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 算术只支持整数 。浮点运算需要 bcawk

算术运算方式对比:

方式 示例 整数 浮点 推荐
$((...)) $((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 返回状态码。

相关推荐
我不是懒洋洋1 小时前
【C++】string(string的成员变量、auto和范围for、string常用接口的说明、OJ题目、string的模拟实现)
c语言·开发语言·c++·visual studio
承渊政道1 小时前
【MySQL数据库学习】(MySQL表的内外连接)
数据库·学习·mysql·leetcode·bash·数据库开发·数据库系统
Brilliantwxx1 小时前
【C++】 C++11 知识点梳理(中)
开发语言·c++
j7~1 小时前
【C++】STL--Vector容器--拆析解剖Vector的实现以及Vector的底层详解(2)
开发语言·c++·动态二维数组·vector深度剖析·vector的实现·杨辉三角形
三品吉他手会点灯8 小时前
C语言学习笔记 - 50.流程控制4 - 流程控制为什么非常非常重要
c语言·开发语言·笔记·学习
在放️11 小时前
Python 爬虫 · 第三方代理接入与合规使用
开发语言·爬虫·python
KANGBboy11 小时前
java知识五(继承)
java·开发语言
c++之路11 小时前
Bazel C++ 构建系列文档(三):构建第一个 C++ 项目
开发语言·c++
AI人工智能+电脑小能手11 小时前
【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?
java·开发语言·面试