第54章:Bash 脚本进阶

第五十四章:Bash 脚本进阶

54.1 字符串处理

Shell 脚本中最常用的数据类型就是字符串,让我们来掌握各种处理技巧!

字符串基础操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
str="Hello World"

# 获取字符串长度
echo ${#str}    # 11

# 截取子串
echo ${str:0:5}       # Hello(从索引0开始,截取5个字符)
echo ${str:6}         # World(从索引6到末尾)
echo ${str:(-5)}      # World(负数索引,用括号更清晰)
# 或者: echo ${str: -5}  # 注意冒号后有空格,容易混淆

# 字符串拼接
str1="Hello"
str2="World"
result="$str1 $str2"
echo $result    # Hello World

字符串替换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
str="Hello World"

# 替换第一个匹配
echo ${str/World/Linux}    # Hello Linux

# 替换所有匹配
echo ${str//o/O}           # HellO WOrld

# 替换开头匹配
echo ${str/#Hello/Hi}      # Hi World

# 替换结尾匹配
echo ${str/%World/Universe}  # Hello Universe

# 删除子串
echo ${str/World}          # Hello (删除第一个 World)
echo ${str//o}             # Hell Wrld(删除所有 o)

字符串大小写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
str="Hello World"

# 转为大写(bash 4.0+)
echo ${str^^}              # HELLO WORLD

# 转为小写
echo ${str,,}             # hello world

# 首字母大写
echo ${str^}               # Hello world

# 模式转换
echo ${str^^[aeiou]}       # HEllO WOrld(只转换元音)

字符串分割

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 按分隔符分割
email="user@example.com"
IFS='@' read -r user domain <<< "$email"
echo "用户: $user"    # user
echo "域名: $domain"  # example.com

# 分割路径
path="/home/user/documents/file.txt"
IFS='/' read -ra parts <<< "$path"
echo "${parts[-1]}"   # file.txt(最后一部分)
echo "${parts[-2]}"  # documents

# 读取每一行
echo -e "line1\nline2\nline3" | while read -r line; do
    echo "行: $line"
done

字符串去空白

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
str="   前后有空格   "

# 去开头空格
echo "${str#"${str%%[![:space:]]*}"}"   # "前后有空格   "

# 去结尾空格
echo "${str%"${str##*[![:space:]]}"}"    # "   前后有空格"

# 去两端空格(bash 内置)
echo "$str" | xargs

# 使用 trim 函数
trim() {
    local var="$*"
    var="${var#"${var%%[![:space:]]*}"}"
    var="${var%"${var##*[![:space:]]}"}"
    echo -n "$var"
}

54.2 正则表达式

正则表达式是文本处理的"瑞士军刀",在 Shell 中经常配合 grep、sed、awk 使用。

正则基础

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 基本元字符
^        # 行首
$        # 行尾
.        # 任意单个字符
*        # 前一个字符零次或多次
+        # 前一个字符一次或多次(扩展正则)
?        # 前一个字符零次或一次
[abc]    # 字符集,匹配 a、b 或 c
[^abc]   # 否定字符集,匹配除了 a、b、c 之外
[a-z]    # 范围,匹配小写字母

扩展正则表达式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 使用 egrep 或 grep -E
grep -E "^[0-9]+$" file.txt      # 纯数字行
grep -E "^[a-zA-Z]+$" file.txt  # 纯字母行
egrep "^hello|world$" file.txt  # hello 开头或 world 结尾

# 其他扩展元字符
|         # 或
()        # 分组
{n}       # 精确重复 n 次
{n,}      # 至少 n 次
{n,m}     # n 到 m 次

常用正则实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 邮箱验证
email="user@example.com"
if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "邮箱格式正确"
fi

# IP 地址验证
ip="192.168.1.1"
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
    echo "IP 格式正确"
fi

# 手机号验证(中国大陆)
phone="13812345678"
if [[ $phone =~ ^1[3-9][0-9]{9}$ ]]; then
    echo "手机号格式正确"
fi

Bash 中的正则匹配

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# [[ ]] 中的 =~ 操作符
str="Hello123World"

if [[ $str =~ ^Hello[0-9]+World$ ]]; then
    echo "匹配成功!"
fi

# 捕获分组
if [[ $str =~ ^([A-Za-z]+)([0-9]+)([A-Za-z]+)$ ]]; then
    echo "全部: ${BASH_REMATCH[0]}"
    echo "第一组: ${BASH_REMATCH[1]}"
    echo "第二组: ${BASH_REMATCH[2]}"
    echo "第三组: ${BASH_REMATCH[3]}"
fi

54.3 sed

sed 是流编辑器,专治"文本替换困难症"!

基本语法

1
sed [选项] '命令' 文件

替换命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 基本替换:s/原内容/新内容/
sed 's/old/new/' file.txt           # 替换每行第一个 old
sed 's/old/new/g' file.txt          # 替换所有 old(g 表示全局)
sed 's/old/new/2' file.txt          # 替换每行第二个 old

# 分隔符可以自定义(当内容包含 / 时)
sed 's|/bin/bash|/bin/sh|' file.txt

# 替换特定行
sed '3s/old/new/' file.txt          # 只替换第3行
sed '1,5s/old/new/g' file.txt        # 替换1-5行
sed '/pattern/s/old/new/g' file.txt  # 只在匹配的行替换

# 多个替换
sed -e 's/old1/new1/' -e 's/old2/new2/' file.txt
sed 's/old1/new1/; s/old2/new2/' file.txt

sed 高级操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 删除行
sed '/^$/d' file.txt                # 删除空行
sed '/^\s*#/d' file.txt             # 删除注释行
sed '1,10d' file.txt                # 删除1-10行

# 插入和追加
sed '3i\新行内容' file.txt          # 在第3行前插入
sed '3a\新行内容' file.txt          # 在第3行后追加
sed '1a\第一行后\n第二行' file.txt  # 多行追加

# 替换整行
sed '/pattern/c\替换后的整行内容' file.txt

# 反向引用
sed 's/\([0-9]\+\)/\1/p' file.txt   # 保留带数字的行
sed 's/\b\(.\)\b/\1\1/g' file.txt   # 每个字符重复一次

# 大小写转换
sed 's/[a-z]/\U&/g' file.txt         # 转大写
sed 's/[A-Z]/\L&/g' file.txt         # 转小写

sed 实战例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 1. 批量替换文件名(在文件内容中)
sed -i 's/旧路径/新路径/g' *.txt     # -i 表示直接修改文件

# 2. 提取 IP 地址
ifconfig | sed -n '/inet /p' | sed 's/inet.*://' | sed 's/ .*//'

# 3. 清理日志(保留最近100行)
sed -i '1,$(($(wc -l<file.log)-100))d' file.log

# 4. 在每行前后加引号
sed 's/.*/"&"/' file.txt            # "每一行"
sed 's/.*/"&"/' file.txt            # "每一行"

54.4 awk

awk 是更强大的文本分析工具,适合处理结构化文本。

基本语法

1
awk 'pattern { action }' 文件

字段操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 默认按空格分割字段
echo "John 25 male Beijing" | awk '{print $1, $3}'
# 输出:John male

# 修改分隔符
echo "192.168.1.1" | awk -F'.' '{print $1"."$2"."$3".0"}'

# 使用多个分隔符
echo "user@email.com,123456" | awk -F'[@,]' '{print $1, $3}'

# 内置变量
# NF: 字段数
# NR: 行号
# $0: 整行
# $n: 第 n 个字段

实战例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 1. 打印文件内容(带行号)
awk '{print NR, $0}' file.txt

# 2. 计算文件大小总和
ls -l *.txt | awk '{sum+=$5} END {print "Total:", sum, "bytes"}'

# 3. 找最高内存占用进程
ps aux | awk 'NR>1 {print $6" "$11}' | sort -rn | head -5

# 4. 格式化输出
awk '{printf "%-10s %5d\n", $1, $2}' file.txt

# 5. 条件过滤
awk '/error/ {print $0}' log.txt           # 包含 error 的行
awk '$3 > 100 {print $1, $3}' file.txt     # 第3字段大于100的行

# 6. 统计访问日志
awk '{print $4}' access.log | sort | uniq -c | sort -rn | head -10

awk 脚本文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 创建 awk 脚本 report.awk
BEGIN {
    FS=":"
    print "=== 用户报告 ==="
}

{
    print "用户: " $1
    print "Shell: " $7
    print "---"
}

END {
    print "=== 报告结束 ==="
}

# 运行
awk -f report.awk /etc/passwd

awk 数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 1. 统计单词出现频率
{ for (i=1; i<=NF; i++) words[$i]++ }
END { for (w in words) print w, words[w] }'

# 2. 按字段分组统计
awk '{sum[$1]+=$2} END {for (k in sum) print k, sum[k]}' data.txt

# 3. 二维数组
awk 'BEGIN {
    array[1,1]="a"; array[1,2]="b"
    array[2,1]="c"; array[2,2]="d"
    for (i=1; i<=2; i++)
        for (j=1; j<=2; j++)
            print i","j":", array[i,j]
}'

54.5 进程处理

查看进程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 查看当前终端的进程
ps

# 查看所有进程
ps aux
ps -ef

# 查看特定进程
ps aux | grep nginx

# 实时监控(top 的简化版)
top -bn1 | head -20

进程控制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 后台运行
./script.sh &

# 查看后台任务
jobs

# 把前台任务放到后台(暂停)
Ctrl+Z

# 恢复后台运行
bg

# 恢复到前台
fg

# 杀死进程
kill PID              # 正常终止
kill -9 PID           # 强制杀死
killall process_name  # 按名字杀

进程等待

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash
# 启动后台进程
./long_task.sh &
pid=$!

# 做其他事情
echo "正在等待后台任务完成..."

# 等待进程结束
wait $pid

echo "任务完成!"

进程信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 获取进程 ID
echo $$

# 获取父进程 ID
echo $PPID

# 查看进程树
pstree
pstree -p username

# 查看打开的文件
lsof -p PID

# 查看进程状态
cat /proc/PID/status

54.6 信号处理

信号是进程间通信的一种方式,Ctrl+C、kill 命令都是发信号。

常用信号

信号编号说明
SIGHUP1挂起
SIGINT2中断(Ctrl+C)
SIGQUIT3退出(Ctrl+\)
SIGKILL9强制杀死
SIGTERM15正常终止
SIGUSR110用户自定义
SIGUSR212用户自定义

trap 命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
# trap_demo.sh

# 捕获 Ctrl+C (SIGINT)
trap 'echo "别按 Ctrl+C,没用的!" ' SIGINT

# 捕获退出信号
trap 'echo "脚本退出中..."; exit 0' EXIT

# 捕获自定义信号
trap 'echo "收到 USR1 信号"' SIGUSR1

echo "按 Ctrl+C 试试?"
echo "PID: $$"

# 无限循环
while true; do
    sleep 1
done

发送信号

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 给进程发送信号
kill -SIGTERM PID
kill -15 PID
kill -SIGUSR1 PID

# 发送自定义信号给当前脚本
kill -SIGUSR1 $$

# 发送信号给进程组
kill -SIGTERM -PGID

54.7 管道与子Shell

管道

管道连接多个命令,数据从左到右流动:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 基本管道
cat file.txt | grep "error" | sort | uniq

# 管道 tee(同时写文件)
cat file.txt | tee output.txt | grep "pattern"

# 管道 xargs(把管道输入转为命令行参数)
cat users.txt | xargs -I {} echo "处理用户: {}"

# 管道组
{ cmd1; cmd2; } | cmd3

子Shell

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 在子Shell中执行(环境隔离)
(cd /tmp; ls)        # 不会改变当前目录
pwd                   # 还是在原目录

# 子Shell 变量隔离
var=100
( var=200; echo "子Shell: $var" )
echo "父Shell: $var"  # 还是 100

# 进程替换
while read -r line; do
    echo "行: $line"
done < <(grep "pattern" file.txt)

子Shell 常用场景

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 1. 临时切换目录
(cd /tmp && do_something)

# 2. 设置临时环境
(env_var=value command)

# 3. 并行执行
./task1.sh &
./task2.sh &
./task3.sh &
wait  # 等待所有后台任务完成

# 4. 避免污染当前环境
(export PATH=/new/path:$PATH; ./script.sh)

54.8 实战脚本

让我们综合运用所学知识,写几个真正有用的脚本!

脚本一:日志分析器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/bin/bash
# log_analyzer.sh - 日志分析工具

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# 检查参数
if [ $# -eq 0 ]; then
    echo "用法: $0 <日志文件>"
    exit 1
fi

LOG_FILE="$1"

if [ ! -f "$LOG_FILE" ]; then
    echo -e "${RED}错误: 文件不存在${NC}"
    exit 1
fi

echo "========================================"
echo "         日志分析报告"
echo "========================================"
echo "文件: $LOG_FILE"
echo "分析时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""

# 总行数
total_lines=$(wc -l < "$LOG_FILE")
echo -e "${GREEN}总行数: $total_lines${NC}"

# 错误统计
error_count=$(grep -c -i "error" "$LOG_FILE" 2>/dev/null || echo 0)
echo -e "${RED}错误 (ERROR): $error_count${NC}"

# 警告统计
warning_count=$(grep -c -i "warn" "$LOG_FILE" 2>/dev/null || echo 0)
echo -e "${YELLOW}警告 (WARN): $warning_count${NC}"

# 成功统计
success_count=$(grep -c -i "success\|ok" "$LOG_FILE" 2>/dev/null || echo 0)
echo -e "${GREEN}成功 (SUCCESS/OK): $success_count${NC}"

# 错误类型分布
echo ""
echo "错误类型分布:"
grep -i "error" "$LOG_FILE" | sed 's/.*\[ERROR\]//' | awk '{print $1}' | sort | uniq -c | sort -rn | head -5

# 最近10条错误
echo ""
echo "最近10条错误:"
grep -i "error" "$LOG_FILE" | tail -10

echo ""
echo "========================================"

脚本二:系统状态监控

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/bin/bash
# system_monitor.sh - 系统状态监控

# 设置阈值
CPU_THRESHOLD=80
MEM_THRESHOLD=80
DISK_THRESHOLD=90

# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

echo "========================================"
echo "         系统状态监控"
echo "========================================"
echo "监控时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""

# CPU 使用率
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
echo -n "CPU 使用率: "
if [ "${cpu_usage%.*}" -gt $CPU_THRESHOLD ]; then
    echo -e "${RED}${cpu_usage}%${NC} [警告: 超过 ${CPU_THRESHOLD}%]"
else
    echo -e "${GREEN}${cpu_usage}%${NC} [正常]"
fi

# 内存使用率
mem_usage=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100}')
echo -n "内存使用率: "
if [ "$mem_usage" -gt $MEM_THRESHOLD ]; then
    echo -e "${RED}${mem_usage}%${NC} [警告: 超过 ${MEM_THRESHOLD}%]"
else
    echo -e "${GREEN}${mem_usage}%${NC} [正常]"
fi

# 磁盘使用率
disk_usage=$(df -h / | tail -1 | awk '{print $5}' | cut -d'%' -f1)
echo -n "磁盘使用率: "
if [ "$disk_usage" -gt $DISK_THRESHOLD ]; then
    echo -e "${RED}${disk_usage}%${NC} [警告: 超过 ${DISK_THRESHOLD}%]"
else
    echo -e "${GREEN}${disk_usage}%${NC} [正常]"
fi

# 负载
load_avg=$(uptime | awk -F'load average:' '{print $2}')
echo "系统负载: $load_avg"

# 在线用户
user_count=$(who | wc -l)
echo "在线用户: $user_count"

# TOP 5 CPU 进程
echo ""
echo "TOP 5 CPU 进程:"
ps aux --sort=-%cpu | head -6 | tail -5 | awk '{print $11, $3"% CPU"}'

# TOP 5 内存进程
echo ""
echo "TOP 5 内存进程:"
ps aux --sort=-%mem | head -6 | tail -5 | awk '{print $11, $4"% MEM"}'

echo "========================================"

脚本三:批量文件处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/bin/bash
# batch_process.sh - 批量文件处理工具

# 配置
INPUT_DIR="./input"
OUTPUT_DIR="./output"
BACKUP_DIR="./backup"
LOG_FILE="batch_process.log"

# 创建目录
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"

# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 处理单个文件
process_file() {
    local file="$1"
    local filename=$(basename "$file")
    
    log "处理文件: $filename"
    
    # 备份原文件
    cp "$file" "$BACKUP_DIR/$filename"
    
    # 处理逻辑(这里示例:转大写 + 行号)
    line_count=$(wc -l < "$file")
    sed -n "1p" "$file" > "$OUTPUT_DIR/$filename"
    sed -n "2,$line_count p" "$file" | \
        sed '/^$/d' | \
        awk '{print NR": "$0}' >> "$OUTPUT_DIR/$filename"
    
    log "完成: $filename -> $OUTPUT_DIR/$filename"
}

# 主逻辑
main() {
    log "========================================"
    log "批量处理开始"
    
    if [ ! -d "$INPUT_DIR" ]; then
        log "错误: 输入目录不存在"
        exit 1
    fi
    
    file_count=0
    for file in "$INPUT_DIR"/*; do
        if [ -f "$file" ]; then
            process_file "$file"
            ((file_count++))
        fi
    done
    
    log "批量处理完成,共处理 $file_count 个文件"
    log "========================================"
}

main "$@"

脚本四:MySQL 数据库备份

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/bin/bash
# mysql_backup.sh - MySQL 数据库备份

# 配置
DB_HOST="localhost"
DB_USER="root"
DB_PASS="your_password"
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
KEEP_DAYS=7

# 创建备份目录
mkdir -p "$BACKUP_DIR"

# 日志
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# 备份函数
backup_database() {
    local db_name="$1"
    local backup_file="$BACKUP_DIR/${db_name}_${DATE}.sql.gz"
    
    log "开始备份数据库: $db_name"
    
    # 执行备份
    mysqldump -h"$DB_HOST" -u"$DB_USER" -p"$DB_PASS" \
        --single-transaction --routines --triggers \
        "$db_name" | gzip > "$backup_file"
    
    if [ $? -eq 0 ]; then
        log "备份成功: $backup_file"
    else
        log "备份失败: $db_name"
        return 1
    fi
}

# 清理旧备份
cleanup_old_backups() {
    log "清理 ${KEEP_DAYS} 天前的备份..."
    find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$KEEP_DAYS -delete
    log "清理完成"
}

# 主逻辑
main() {
    log "========================================"
    log "MySQL 数据库备份开始"
    
    # 获取所有数据库
    databases=$(mysql -h"$DB_HOST" -u"$DB_USER" -p"$DB_PASS" \
        -e "SHOW DATABASES;" | grep -v Database | grep -v information_schema)
    
    for db in $databases; do
        backup_database "$db"
    done
    
    cleanup_old_backups
    
    # 显示备份文件
    log "当前备份文件:"
    ls -lh "$BACKUP_DIR"
    
    log "========================================"
    log "备份完成"
}

main "$@"

本章小结

本章我们学习了 Bash 脚本的进阶内容:

知识点说明
字符串处理截取、替换、大小写、分割、去空白
正则表达式模式匹配、验证、文本提取
sed流编辑器,文本替换、删除、插入
awk文本分析工具,字段处理、统计
进程处理查看、控制、等待、信号
信号处理trap 捕获,自定义信号处理
管道与子Shell命令组合、环境隔离
实战脚本日志分析、系统监控、批处理、备份

掌握这些技能后,你的 Shell 脚本能力将提升到一个新境界!能够处理复杂的文本分析、自动化运维等任务。


💡 温馨提示: 正则表达式和 sed/awk 是 Linux 文本处理的三剑客。它们看起来复杂,但用多了就熟练了。建议找一些日志文件多加练习,你会感受到"原来文本处理可以这么快!"


第五十四章:Bash 脚本进阶 — 完结! 🎉

下一章我们将进入"版本控制"的世界,学习 Git 版本管理系统。敬请期待! 🚀

最后修改 March 24, 2026: 新增JavaScript教程 (37305c4)