Shell 学习笔记

Claude Code 学习笔记

分类:基础知识

核心主题:Shell / Bash 脚本编程完整学习笔记

主要内容:全面系统地讲解 Shell 编程,涵盖 Bash/Zsh 基础命令、文件操作、文本处理、脚本编程、函数、正则、环境配置及在 Claude Code 中的应用。

关键词:Shell, Bash, Zsh, 命令行, Linux, 终端, 脚本编程, 文件操作, 文本处理

目录

  1. Shell 概述与选择
  2. Shell 基础操作
  3. 文本处理利器
  4. 输入输出与管道
  5. Shell 脚本编程基础
  6. 流程控制
  7. 函数与作用域
  8. 正则表达式
  9. Shell 环境配置
  10. Shell 调试与最佳实践
  11. 与 Claude Code 的结合
  12. 核心要点总结

一、Shell 概述与选择

1.1 什么是 Shell

Shell 是操作系统与用户之间的命令行接口(Command-Line Interface, CLI)。它既是一种命令解释器,也是一种强大的脚本编程语言。用户在 Shell 中输入命令,Shell 将其解释并传递给操作系统内核执行,然后将结果返回给用户。

Shell 的本质

Shell 本质上是一个用 C 语言编写的程序,它是用户使用 Linux/Unix 系统的桥梁。Shell 既负责交互式地执行命令,也能够批量执行脚本文件中的命令序列。常见的 Shell 实现包括 Bash、Zsh、Fish、Dash 等。

1.2 Shell 的历史演进

Shell 的发展历程是 Unix/Linux 系统发展的一个缩影:

年代Shell开发者特点
1971Thompson ShellKen Thompson最早的 Unix Shell,功能简单
1977Bourne Shell (sh)Stephen Bourne引入变量、控制流,成为标准 Unix Shell
1989Bash (Bourne Again Shell)Brian Fox兼容 sh,增加行编辑、历史、补全等功能
1990Zsh (Z Shell)Paul Falstad强大的补全、主题、插件系统
2005Fish (Friendly Interactive Shell)Axel Liljencrantz开箱即用,语法高亮,自动建议
2016Dash (Debian Almquist Shell)Herbert Xu轻量快速,POSIX 兼容,用于系统启动
核心事实:Bash 是目前 Linux 和 macOS 系统上的默认 Shell,也是绝大多数服务器环境的标准配置。Zsh 自 macOS Catalina (2019) 起成为 macOS 的默认 Shell。理解 Bash 就等于理解了绝大多数 Shell 环境下的工作方式。

1.3 主流 Shell 对比

特性BashZshFishDash
POSIX 兼容是(默认模式)部分(sh 模拟)完全
脚本兼容性极好(行业标准)良好差(语法不兼容)极好
交互体验良好优秀优秀
插件/主题有限(bash-it)丰富(oh-my-zsh)内置
自动补全基础强大可编程自动建议基础
性能中等较慢(插件多时)中等极快
适用场景通用/脚本/服务器开发环境新手/桌面系统脚本/启动

学习建议

初学者建议从 Bash 开始学习,因为 Bash 是 Linux 服务器的标准 Shell,掌握 Bash 后可以在任何 Linux 环境中工作。Zsh 在交互体验上更优,适合作为日常开发环境,但脚本编写仍建议遵循 Bash 语法以确保最大兼容性。

1.4 在 Windows 上使用 Shell

Windows 用户可以通过以下方式获得 Shell 环境:

方式优点缺点推荐指数
WSL 2 (Windows Subsystem for Linux)完整的 Linux 内核,原生性能,与 Windows 文件系统互操作需开启虚拟化,占用磁盘空间五星
Git Bash安装 Git 时自带,轻量级,开箱即用功能有限,部分命令缺失四星
Cygwin丰富的 Unix 工具移植性能开销大,配置复杂三星
MSYS2包管理,较新工具链社区相对小三星
PowerShellWindows 原生,支持 .NET 对象管道语法与 Unix Shell 完全不同二星(替代方案)

1.5 查看和切换当前 Shell

以下命令用于查看和切换当前使用的 Shell:

# 查看当前 Shell echo "$SHELL" # 查看可用 Shell 列表 cat /etc/shells # 查看当前登录 Shell echo "$0" # 查看 Shell 版本 bash --version zsh --version # 切换默认 Shell(chsh = change shell) chsh -s /bin/zsh # 切换到 Zsh chsh -s /bin/bash # 切换到 Bash
$ echo "$SHELL" /bin/bash $ cat /etc/shells /bin/sh /bin/bash /usr/bin/bash /bin/zsh /usr/bin/zsh /bin/dash $ bash --version GNU bash,版本 5.2.15(1)-release (x86_64-pc-linux-gnu)

二、Shell 基础操作

2.1 终端打开方式

在不同平台上打开终端(命令行窗口)的方法:

平台快捷键/方法
Linux (GNOME)Ctrl + Alt + T
Linux (KDE)Ctrl + Alt + Space 或从菜单启动 Konsole
macOSCmd + Space 搜索 "Terminal"
Windows (WSL)Win + R 输入 wsl
Windows (Git Bash)右键菜单 "Git Bash Here"
VS CodeCtrl + ` (反引号) 打开内置终端

高效技巧

在 VS Code 中,可以使用 Ctrl + Shift + ` 创建新终端,Ctrl + Shift + 5 拆分终端面板。同时按下 Ctrl + D 可以关闭当前终端会话。

2.2 命令结构

Shell 命令的基本结构遵循统一的格式:

命令结构:command [options] [arguments]
  • command -- 命令名,如 ls, cd, grep
  • options -- 选项,用于修改命令行为,通常以 - (短选项) 或 -- (长选项) 开头
  • arguments -- 参数,命令操作的目标
# 命令示例:列出当前目录所有文件(包括隐藏文件),以长格式显示 ls -la # 分解:ls 是命令,-l 是长格式,-a 是显示隐藏文件,合写为 -la # 更多示例 cp -r /path/to/source /path/to/dest # 递归复制目录 grep -rin "pattern" ./ # 递归搜索忽略大小写

2.3 文件系统导航

掌握文件系统的导航是使用 Shell 的第一步:

命令含义示例
pwdPrint Working Directory,显示当前工作目录的绝对路径pwd/home/user/projects
lsList,列出目录内容ls -la /home
cdChange Directory,切换目录cd ~/Documents
pushd压入目录栈并切换pushd /tmp
popd从目录栈弹出并切换popd
dirs显示目录栈内容dirs -v(带序号)
# 导航常用技巧 cd # 回到用户主目录 cd ~ # 同上,~ 代表用户主目录 cd - # 回到上一个目录 cd .. # 回到上级目录 cd ../.. # 回到上两级目录 # pushd/popd 示例:在多个目录间快速切换 pushd /var/log # 保存当前目录并进入 /var/log pushd /etc # 保存 /var/log 并进入 /etc dirs -v # 显示目录栈 popd # 回到 /var/log popd # 回到最初目录

ls 命令详解

ls 是最常用的命令之一,常用选项:

  • -l -- 长格式显示(权限、链接数、所有者、大小、时间)
  • -a -- 显示所有文件(包括以 . 开头的隐藏文件)
  • -h -- 人类可读的文件大小(配合 -l 使用)
  • -t -- 按修改时间排序
  • -r -- 反向排序
  • -S -- 按文件大小排序
  • -R -- 递归列出子目录

2.4 文件操作

文件操作是 Shell 最基础也是最重要的功能:

命令功能常用选项示例
touch创建空文件或更新文件时间戳touch file.txt
mkdir创建目录-p 递归创建mkdir -p a/b/c
cp复制文件/目录-r 递归, -i 交互, -v 详细cp -rv src/ dest/
mv移动/重命名文件-i 交互, -v 详细mv old.txt new.txt
rm删除文件/目录-r 递归, -f 强制, -i 交互rm -rf temp/
ln创建链接-s 符号链接(软链接)ln -s target link

危险命令警告

rm -rf / 会递归强制删除根目录下所有文件,导致系统崩溃。在生产环境中请务必小心使用 rm 命令,尤其是加上 -rf 选项时。建议:

  • 删除前先用 ls 确认目标
  • 在重要目录中使用 rm -i 启用交互确认
  • 考虑使用 trash-cli 代替 rm (放入回收站而非直接删除)

2.5 查看文件内容

命令用途示例
cat连接并显示文件全部内容cat file.txt
less分页查看(支持上下翻页、搜索)less large.log
more分页查看(仅支持下翻)more file.txt
head显示文件开头(默认 10 行)head -n 20 file.txt
tail显示文件末尾(默认 10 行)tail -f app.log
wc统计行/词/字符数wc -l file.txt
# 实用组合 # 实时监控日志文件(Ctrl+C 退出) tail -f /var/log/syslog # 查看文件的前 5 行和后 5 行 head -5 file.txt tail -5 file.txt # 统计代码行数(排除空行和注释) grep -v '^\s*$\|^\s*#' script.sh | wc -l # 使用 less 浏览时常用快捷键 # /pattern -- 向下搜索 # ?pattern -- 向上搜索 # n -- 下一个匹配 # N -- 上一个匹配 # q -- 退出 # g -- 跳转到文件开头 # G -- 跳转到文件末尾

2.6 文件权限管理

Linux/Unix 系统的文件权限模型是安全的基础。每个文件和目录都有三组权限(所有者、所属组、其他人),每组包含读(r)、写(w)、执行(x) 三个权限位。

# 查看文件权限 ls -l # 输出示例: -rwxr-xr-- 1 user group 4096 Mar 15 10:30 script.sh # ^^^ ^^^ ^^^ # user group other # 权限的数字表示法 # r=4, w=2, x=1, -=0 # rwx = 7, r-x = 5, r-- = 4, --- = 0 # 修改权限 chmod 755 script.sh # rwxr-xr-x chmod +x script.sh # 添加执行权限(保留其他权限) chmod -w config.ini # 移除写权限 # 修改所有者 chown user:group file.txt # 同时修改所有者和所属组 chown -R user:group dir/ # 递归修改目录下所有文件 # 设置默认权限掩码 umask # 查看当前掩码(如 0022) umask 0022 # 设置掩码(新文件默认权限 = 666 - umask)
权限速查表:最常见的权限组合及含义
  • -rwxr-xr-x (755) -- 程序文件,所有人可执行
  • -rw-rw-r-- (664) -- 数据文件,同组可写
  • -rw------- (600) -- 私密文件(如 SSH 私钥)
  • -rw-r--r-- (644) -- 普通文件默认权限
  • drwxr-xr-x (755) -- 目录默认权限

2.7 帮助系统

Shell 提供了完整的帮助系统:

# 查看命令的手册页(最权威的参考) man ls man bash # Bash 本身的完整文档(非常详细) man 5 crontab # 查看 crontab 配置文件的格式(第 5 节) # 简略帮助 ls --help # 查看命令的内置帮助(仅 Bash 内置命令) help cd help source # 查看命令类型(内置命令还是外部程序) type cd # cd is a shell builtin type grep # grep is /usr/bin/grep # whatis:一行描述 whatis ls # ls (1) - list directory contents # apropos:搜索手册页 apropos compress # 搜索所有与 compress 相关的手册页

man 页面导航

在 man 页面中,使用 Space 向下翻页,b 向上翻页,/pattern 搜索,q 退出。man man 可以查看 man 命令自己的帮助。

三、文本处理利器

文本处理是 Shell 最强大的功能之一。Linux/Unix 哲学强调"一切皆文件",而文本处理命令就是对文件内容的操作工具。

3.1 grep:文本搜索

grep (Global Regular Expression Print) 是 Shell 中最常用的文本搜索工具,它使用正则表达式对文本进行模式匹配。

# 基本用法 grep "pattern" file.txt # 在文件中搜索 grep "error" *.log # 搜索所有 .log 文件 # 常用选项 grep -i "error" log.txt # 忽略大小写 grep -v "debug" log.txt # 反向匹配(不包含 debug 的行) grep -n "TODO" src/*.py # 显示行号 grep -c "function" script.sh # 统计匹配行数 grep -r "main" ./src/ # 递归搜索目录 grep -l "config" ./src/*.js # 只显示匹配的文件名 grep -w "class" *.java # 匹配整个单词 # 上下文显示 grep -B 5 "ERROR" log.txt # 显示匹配前 5 行 (Before) grep -A 10 "Exception" log.txt # 显示匹配后 10 行 (After) grep -C 3 "failed" log.txt # 显示前后各 3 行 (Context)
grep 实用场景:
  • 日志分析:grep -E "ERROR|FATAL" app.log 快速定位错误
  • 代码搜索:grep -rn "function_name" --include="*.py" 查找函数定义
  • 进程过滤:ps aux | grep nginx 查找特定进程
  • 配置检查:grep -v "^\s*#\|^\s*$" config.conf 查看有效配置

3.2 sed:流编辑器

sed (Stream Editor) 是非交互式的流编辑器,主要用于对文本进行替换、删除、插入等操作。它一次处理一行内容,处理完一行后再处理下一行。

# 替换操作(最常用) sed 's/old/new/' file.txt # 替换每行第一个匹配 sed 's/old/new/g' file.txt # 替换全部匹配 (global) sed 's/old/new/2' file.txt # 替换每行第二个匹配 sed 's/old/new/gi' file.txt # 全部替换,忽略大小写 # 带行号范围的替换 sed '1,10s/foo/bar/g' file.txt # 只在 1-10 行替换 sed '/^#/s/enabled/disabled/g' # 只在以#开头的行替换 # 删除操作 sed '3d' file.txt # 删除第 3 行 sed '5,10d' file.txt # 删除 5-10 行 sed '/^#/d' file.txt # 删除所有注释行 sed '/^$/d' file.txt # 删除所有空行 # 插入/追加 sed '2i\插入的行内容' file.txt # 在第 2 行前插入 (insert) sed '2a\追加的行内容' file.txt # 在第 2 行后追加 (append) # 原地修改(直接修改文件而不是输出到 stdout) sed -i 's/old/new/g' file.txt # GNU sed (Linux) sed -i '' 's/old/new/g' file.txt # BSD sed (macOS) # 备份后修改 sed -i.bak 's/old/new/g' file.txt # 创建 file.txt.bak 备份

sed 命令格式详解

sed 命令的基本格式为:[地址范围]操作[参数]

  • 地址范围:可以是行号(5)、行号范围(1,10)、模式匹配(/pattern/
  • 操作命令s 替换、d 删除、p 打印、i 插入、a 追加
  • 标志位g 全局、i 忽略大小写、p 打印

3.3 awk:文本分析工具

awk 是以三位创始人(Aho, Weinberger, Kernighan)命名的文本分析工具,擅长处理结构化文本(如按列分隔的数据)。

# awk 基本结构:awk 'pattern { action }' file # 打印特定列(默认以空白分隔) awk '{ print $1, $3 }' file.txt # 打印第 1 和第 3 列 awk '{ print $NF }' file.txt # 打印最后一列 awk '{ print NR, $0 }' file.txt # 显示行号 (NR) 和整行 ($0) # 指定分隔符 awk -F ',' '{ print $1, $2 }' data.csv # 以逗号分隔(CSV 文件) awk -F ':' '{ print $1 }' /etc/passwd # 以冒号分隔 # 模式匹配 awk '/error/ { print }' log.txt # 打印包含 error 的行 awk '$3 > 100 { print $1, $3 }' data # 第 3 列大于 100 的行 # BEGIN 和 END 块 awk 'BEGIN { print "开始处理" } { sum += $1 } END { print "总和:", sum }' numbers.txt # 内置变量 awk '{ print FILENAME, FNR, $0 }' *.txt # 文件名、行号、内容
# awk 实战示例 # 1. 计算文件大小总和 ls -l *.log | awk '{ sum += $5 } END { print "总大小:", sum, "字节" }' # 2. 按 IP 统计访问次数(分析 Nginx 日志) awk '{ ip[$1]++ } END { for (i in ip) print i, ip[i] }' access.log | sort -k2 -rn | head -10 # 3. 格式化输出 awk '{ printf "%-20s %8s\n", $1, $2 }' file.txt # 4. 条件统计 awk '$1 ~ /^[0-9]+$/ { print }' file.txt # 第一列为数字的行

3.4 sort, uniq, cut, tr, paste, join

命令功能常用选项
sort对文本行排序-n 数字排序, -r 倒序, -k 指定列, -u 去重
uniq去重(需要先 sort)-c 计数, -d 只显示重复行, -u 只显示唯一行
cut按列/字符截取-d 分隔符, -f 列号, -c 字符范围
tr字符转换/删除-d 删除, -s 压缩重复, [:upper:] 等字符类
paste合并文件(按列拼接)-d 分隔符, -s 串行合并
join按公共字段合并文件-t 分隔符, -1 文件1的字段, -2 文件2的字段
# sort 示例 sort -k2 -n scores.txt # 按第 2 列数字排序 sort -t: -k3 -rn /etc/passwd # 以:为分隔,按第3列倒序 # uniq 示例 sort words.txt | uniq -c | sort -rn # 词频统计(经典组合) sort access.log | uniq -d # 找出重复行 # cut 示例 cut -d: -f1,3 /etc/passwd # 取用户名和 UID cut -c1-10 file.txt # 取每行前 10 个字符 # tr 示例 cat file.txt | tr 'a-z' 'A-Z' # 小写转大写 cat file.txt | tr -d '\r' # 删除 Windows 回车符(\r\n → \n) cat file.txt | tr -s ' ' # 压缩连续空格为一个 # paste 示例 paste -d, file1.txt file2.txt # 用逗号拼接两个文件

3.5 diff 和 patch

diff 用于比较两个文件的差异,patch 用于将差异应用到文件。这两个命令是版本控制的基础。

# 比较文件 diff file1.txt file2.txt # 标准差异输出 diff -u file1.txt file2.txt # unified 格式(更可读) diff -r dir1/ dir2/ # 递归比较目录 # 生成补丁文件 diff -u old.py new.py > fix.patch # 应用补丁 patch -p1 < fix.patch # 应用补丁(-p1 去掉路径前缀) patch -R < fix.patch # 回滚补丁

3.6 综合实战示例

# 实战 1:分析 Nginx 访问日志,统计 TOP 10 IP 地址 awk '{ ips[$1]++ } END { for (ip in ips) print ips[ip], ip }' access.log \ | sort -rn | head -10 # 实战 2:查找项目中所有 TODO 注释并汇总 grep -rn "TODO\|FIXME\|HACK" --include="*.py" --include="*.js" ./src \ | awk -F: '{ printf "%-30s %-4s %s\n", $1, $2, $0 }' # 实战 3:批量替换文件名中的空格 for f in *.txt; do mv "$f" "${f// /_}"; done # 实战 4:日志时间范围过滤 awk '$0 ~ /^2026-05-04/ && $0 ~ /1[0-5]:/' app.log > morning.log # 实战 5:CSV 文件列统计 awk -F, 'NR>1 { sum[$2] += $3; count[$2]++ } END { for (k in sum) printf "%s: 总和=%d, 平均=%.2f\n", k, sum[k], sum[k]/count[k] }' sales.csv

四、输入输出与管道

4.1 标准输入/输出/错误

Linux/Unix 系统中,每个进程默认打开三个文件描述符:

文件描述符名称符号默认目标
0标准输入 (stdin)<键盘
1标准输出 (stdout)>1>屏幕
2标准错误 (stderr)2>屏幕

理解 I/O 重定向

标准输出和标准错误都默认输出到屏幕,但它们是独立的。错误信息输出到 stderr 可以确保即使 stdout 被重定向到文件,错误仍然显示在屏幕上。

4.2 重定向操作符

# 输出重定向 command > file.txt # 将 stdout 写入文件(覆盖) command >> file.txt # 将 stdout 追加到文件 # 输入重定向 command < file.txt # 从文件读取 stdin # 错误重定向 command 2> error.log # 将 stderr 写入文件 command 2>> error.log # 将 stderr 追加到文件 # 同时重定向 stdout 和 stderr command &> output.log # Bash 4+: stdout + stderr 到同一文件 command > output.log 2>&1 # 兼容写法(先重定向 stdout,再将 stderr 指向 stdout) command >> output.log 2>&1 # 追加模式 # 丢弃输出 command > /dev/null # 丢弃 stdout command > /dev/null 2>&1 # 丢弃所有输出 # 从多个文件输入(cat 命令) cat file1.txt file2.txt > combined.txt

4.3 管道

管道 (|) 是 Shell 中最强大的特性之一。它可以将一个命令的 stdout 连接到另一个命令的 stdin,形成命令处理链。

管道哲学:每个命令做好一件事,通过管道组合成强大的处理流程。这正是 Unix 哲学的核心 -- "Do one thing and do it well"。
# 经典管道链 # 统计某个进程的线程数 ps aux | grep nginx | grep -v grep | wc -l # 找出最大的 5 个文件 ls -lhS | head -6 # 查看最近的日志并过滤 tail -f app.log | grep --line-buffered "ERROR" # 多层管道:统计文本中最常用的 10 个单词 cat file.txt \ | tr -cs '[:alpha:]' '\n' \ | tr 'A-Z' 'a-z' \ | sort \ | uniq -c \ | sort -rn \ | head -10

管道 vs 重定向

管道连接的是命令和命令,将一个命令的输出作为另一个命令的输入。
重定向连接的是命令和文件,将命令的输出写入文件或从文件读取输入。
两者可以混合使用:grep error log.txt | sort > sorted_errors.txt

4.4 tee:同时输出到文件和屏幕

# tee 将 stdout 同时写入文件和屏幕 command | tee output.txt # 覆盖文件 command | tee -a output.txt # 追加到文件 command | tee output.txt | grep error # 保存中间结果并继续处理

4.5 xargs:构建和执行命令

xargs 从 stdin 读取数据,将其作为参数传递给指定命令。这是处理大量文件时的利器。

# 基本用法 find . -name "*.log" | xargs rm # 删除所有 .log 文件 find . -name "*.py" | xargs wc -l # 统计所有 Python 文件行数 # 常用选项 find . -name "*.bak" -print0 | xargs -0 rm # 处理文件名含空格的情况 seq 1 10 | xargs -I {} echo "Number: {}" # 占位符替换 cat urls.txt | xargs -P 4 -I {} curl {} # 并行 4 个进程下载 xargs -n 2 < pairs.txt # 每次传 2 个参数

4.6 Here Document

Here Document 是一种在脚本中嵌入多行文本的机制:

# Here Document 基本语法:command << DELIMITER # 示例 1:写入文件 cat << EOF > greetings.txt Hello, World! 这是一个多行文本。 EOF # 示例 2:执行 SQL mysql -u root -p << SQL USE mydatabase; SELECT * FROM users WHERE age > 18; SQL # 示例 3:使用带引号的 DELIMITER 禁止变量展开 var="world" cat << 'EOF' # 单引号阻止 $var 展开 Hello $var EOF # 输出: Hello $var # 示例 4:<<- 忽略前导 Tab if true; then cat <<- END This line is indented with tabs. END fi

五、Shell 脚本编程基础

5.1 脚本文件结构与 Shebang

Shell 脚本是以 .sh 结尾的文本文件,首行指定解释器路径:

#!/bin/bash # 这是注释 echo "Hello, Shell!"
Shebang含义适用场景
#!/bin/bash使用 Bash 解释执行通用脚本,兼容性最好
#!/usr/bin/env bash从 PATH 中查找 Bash可移植性更好(推荐)
#!/bin/sh使用 POSIX Shell追求最大兼容性
#!/usr/bin/env python3使用 Python 3多语言脚本
脚本执行方式:
  • ./script.sh -- 需要执行权限(chmod +x script.sh
  • bash script.sh -- 不需要执行权限,作为 bash 参数
  • source script.sh. script.sh -- 在当前 Shell 中执行

5.2 变量

# 变量定义(等号两边不能有空格) name="John" age=25 greeting="Hello, $name" # 双引号中变量会展开 greeting2='Hello, $name' # 单引号中变量不会展开 # 变量引用 echo $name # 基本引用 echo "${name}" # 花括号引用(推荐,避免歧义) echo "${name}_suffix" # 花括号必须的场合 # 只读变量 readonly PI=3.14159 # 删除变量 unset name # 变量默认值 echo "${var:-默认值}" # 如果 var 未设置,使用默认值(不改变 var) echo "${var:=默认值}" # 如果 var 未设置,设置 var 为默认值 echo "${var:?错误信息}" # 如果 var 未设置,输出错误信息并退出 echo "${var:+替代值}" # 如果 var 已设置,使用替代值

5.3 特殊变量

变量含义示例
$0脚本名称./script.sh
$1, $2, ...位置参数(第 1 个、第 2 个...)script.sh arg1 arg2
$#位置参数的数量2
$@所有参数(每个参数独立引用)"arg1" "arg2"
$*所有参数(作为单个字符串)"arg1 arg2"
$?上一条命令的退出码(0 成功,非 0 失败)0
$$当前脚本的 PID12345
$!最后一个后台命令的 PID12346
$_上一条命令的最后一个参数
$LINENO当前行号(用于调试)42

5.4 字符串操作

# 字符串拼接 str1="Hello" str2="World" result="${str1} ${str2}" # "Hello World" # 字符串长度 echo "${#str1}" # 5 # 子字符串截取 echo "${str:0:5}" # 从索引 0 取 5 个字符 echo "${str:3}" # 从索引 3 取到末尾 echo "${str: -3}" # 取最后 3 个字符(注意空格) # 字符串替换 echo "${str/old/new}" # 替换第一个匹配 echo "${str//old/new}" # 替换全部匹配 echo "${str/#prefix/new}" # 替换开头匹配 echo "${str/%suffix/new}" # 替换结尾匹配 # 前缀/后缀删除 filename="archive.tar.gz" echo "${filename#*.}" # tar.gz (删除最短前缀 *.) echo "${filename##*.}" # gz (删除最长前缀 *.) echo "${filename%.*}" # archive.tar(删除最短后缀 .*) echo "${filename%%.*}" # archive (删除最长后缀 .*) # 大小写转换(Bash 4+) echo "${str,,}" # 全部小写 echo "${str^^}" # 全部大写

5.5 数组

# 定义数组 fruits=("apple" "banana" "cherry") numbers=([0]=10 [1]=20 [5]=50) # 稀疏数组 # 访问元素 echo "${fruits[0]}" # apple echo "${fruits[@]}" # 所有元素 # 数组长度 echo "${#fruits[@]}" # 3 # 添加元素 fruits+=("date" "elderberry") # 遍历数组 for fruit in "${fruits[@]}"; do echo "$fruit" done # 键值关联数组(Bash 4+) declare -A user user=([name]="John" [age]=30 [city]="Shanghai") echo "${user[name]}"

5.6 算术运算

# 方法 1:$(( )) (推荐) a=10 b=3 echo $(( a + b )) # 13 echo $(( a - b )) # 7 echo $(( a * b )) # 30 echo $(( a / b )) # 3(整数除法) echo $(( a % b )) # 1(取模) echo $(( a ** b )) # 1000(幂运算,Bash 4+) # 方法 2:let 命令 let c=a+b let d+=5 # 方法 3:expr(外部命令,较慢) expr 10 + 3 # 浮点数运算(使用 bc 或 awk) echo "scale=2; 10/3" | bc # 3.33 awk 'BEGIN { printf "%.2f", 10/3 }' # 3.33 # 自增/自减 echo $(( ++a )) # 前置自增 echo $(( a++ )) # 后置自增 # 位运算 echo $(( a << 2 )) # 左移 2 位 echo $(( a & b )) # 按位与 echo $(( a | b )) # 按位或

六、流程控制

6.1 if/then/elif/else/fi

# if 基本结构 if [ "$age" -ge 18 ]; then echo "成年人" elif [ "$age" -ge 60 ]; then echo "老年人" else echo "未成年人" fi # 条件表达式比较 # 文件判断 if [ -f "$file" ]; then echo "是普通文件"; fi if [ -d "$dir" ]; then echo "是目录"; fi if [ -e "$path" ]; then echo "路径存在"; fi if [ -s "$file" ]; then echo "文件非空"; fi if [ -r "$file" ]; then echo "文件可读"; fi if [ -w "$file" ]; then echo "文件可写"; fi if [ -x "$file" ]; then echo "文件可执行"; fi # 字符串比较 if [ "$str1" = "$str2" ]; then echo "相等"; fi if [ "$str1" != "$str2" ]; then echo "不等"; fi if [ -z "$str" ]; then echo "空字符串"; fi if [ -n "$str" ]; then echo "非空字符串"; fi # 数字比较 if [ "$a" -eq "$b" ]; then echo "等于"; fi # -eq, -ne, -gt, -ge, -lt, -le

6.2 test 命令和 [[ ]]

[ ] 与 [[ ]] 的区别:
  • [ ]test 命令的简写,POSIX 兼容
  • [[ ]] 是 Bash/Zsh 的关键字,功能更强:支持 && || =~(正则匹配)
  • [[ ]] 不需要对变量加引号(自动处理空值)
  • 可移植脚本用 [ ],Bash 专属脚本推荐 [[ ]]
# [[ ]] 高级用法 if [[ "$str" == *.txt ]]; then # 通配符匹配 echo "是 txt 文件" fi if [[ "$str" =~ ^[0-9]+$ ]]; then # 正则匹配 echo "全是数字" fi if [[ "$a" -gt 0 && "$b" -lt 100 ]]; then # 逻辑与 echo "a 在范围内" fi if [[ "$user" == "admin" || "$user" == "root" ]]; then # 逻辑或 echo "是管理员" fi

6.3 case/esac

case "$1" in start|Start) echo "启动服务..." systemctl start myservice ;; stop|Stop) echo "停止服务..." systemctl stop myservice ;; restart|Restart) echo "重启服务..." systemctl restart myservice ;; status|Status) systemctl status myservice ;; *) echo "用法: $0 {start|stop|restart|status}" exit 1 ;; esac

6.4 for 循环

# 列表循环 for color in red green blue; do echo "颜色: $color" done # C 风格循环 for ((i=0; i<10; i++)); do echo "计数: $i" done # 文件遍历 for file in *.txt; do echo "处理文件: $file" wc -l "$file" done # 命令结果遍历 for user in $(cut -d: -f1 /etc/passwd); do echo "用户: $user" done # 数组遍历 arr=("one" "two" "three") for item in "${arr[@]}"; do echo "$item" done # 关联数组遍历 declare -A map=([a]=1 [b]=2 [c]=3) for key in "${!map[@]}"; do echo "$key = ${map[$key]}" done # seq 生成序列 for i in $(seq 1 2 10); do # 1, 3, 5, 7, 9 echo "$i" done

6.5 while 和 until 循环

# while 循环 count=1 while [ "$count" -le 5" ]; do echo "第 $count 次" count=$((count+1)) done # 逐行读取文件 while IFS= read -r line; do echo "行: $line" done < "file.txt" # until 循环(条件为真时停止) until ping -c1 google.com &> /dev/null; do echo "等待网络就绪..." sleep 2 done echo "网络已连接" # 无限循环 + break while true; do echo "按 Ctrl+C 退出" sleep 1 done

6.6 break 和 continue

关键字作用用法
break跳出当前循环breakbreak n(跳出 n 层循环)
continue跳过本次循环的剩余部分continuecontinue n
# break 示例:找到第一个空行 line_number=0 while IFS= read -r line; do line_number=$((line_number+1)) if [ -z "$line" ]; then echo "第一个空行在第 $line_number 行" break fi done < file.txt # continue 示例:跳过注释行 while IFS= read -r line; do [[ "$line" == #* ]] && continue echo "$line" done < config.txt

循环模式选择

  • for ... in ... -- 遍历已知集合(文件列表、数组等)
  • for ((...)) -- 需要计数器的场景
  • while read -- 逐行处理文件(最安全的文件读取方式)
  • while true -- 守护进程、轮询等无限循环
  • until -- 等待某个条件满足的场景

七、函数与作用域

7.1 函数定义与调用

# 两种定义方式 # 方式 1 function greet { echo "Hello, $1!" } # 方式 2(POSIX 兼容,推荐) greet() { local name="$1" echo "Hello, ${name}!" } # 调用函数 greet "World"

7.2 参数传递与返回值

#!/usr/bin/env bash # 函数参数(与脚本参数类似) print_info() { echo "函数名: ${FUNCNAME[0]}" echo "参数个数: $#" echo "所有参数: $@" echo "第一个参数: $1" echo "第二个参数: $2" } print_info "foo" "bar" "baz" # 返回值(退出码方式,0-255) is_even() { if [ $(( $1 % 2 )) -eq 0 ]; then return 0 # 真 else return 1 # 假 fi } if is_even 42; then echo "是偶数" fi # 通过 stdout 返回值 get_username() { echo "john_doe" # 通过 echo 返回字符串 } user=$(get_username) # 命令替换捕获输出 echo "用户名: $user"

7.3 局部变量 local

#!/usr/bin/env bash # 全局变量 vs 局部变量 global_var="我是全局的" test_scope() { local local_var="我是局部的" global_var="函数中修改了全局变量" echo "函数内: $local_var" } test_scope echo "函数外: $global_var" echo "函数外访问 local_var: $local_var" # 空值,不可见

作用域规则

  • 不加 local 的变量默认是全局变量
  • local 只能在函数内使用
  • 函数可以修改全局变量的值
  • 函数的 $1, $2 等是函数的参数,与脚本参数隔离

7.4 函数库组织与 source

# lib/common.sh -- 函数库文件 # 提供通用工具函数 log_info() { echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $*" } log_error() { echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $*" >&2 } die() { log_error "$*" exit 1 } confirm() { read -r -p "$* [y/N]: " response [[ "$response" =~ ^[Yy]$ ]] }
# main.sh -- 主脚本,加载函数库 #!/usr/bin/env bash # 加载函数库 source "$(dirname "$0")/lib/common.sh" # 或使用 . 命令(POSIX 兼容) . "$(dirname "$0")/lib/common.sh" log_info "脚本开始执行" confirm "是否继续?" || die "用户取消了操作" log_info "脚本执行完毕"

函数库组织最佳实践

  • 将函数库放在 lib/ 目录中
  • 使用 $(dirname "$0") 获取脚本所在目录
  • 函数库中只定义函数,不执行代码
  • 使用有意义的函数名前缀(如 log_, file_
  • 在函数库顶部添加使用说明注释

7.5 递归函数示例

# 递归计算阶乘 factorial() { if [ "$1" -le 1" ]; then echo 1 else local prev=$(factorial $(( $1 - 1 ))) echo $(( $1 * prev )) fi } factorial 5 # 输出 120 # 递归遍历目录 tree_list() { local dir="$1" local prefix="$2" local files=("$dir"/*) local i=0 for f in "${files[@]}"; do local name=$(basename "$f") local is_last=$(( i == ${#files[@]} - 1 )) if [ -d "$f" ]; then echo "${prefix}$([ "$is_last" = 1 ] && echo '└──' || echo '├──') $name/" local new_prefix="${prefix}$([ "$is_last" = 1 ] && echo ' ' || echo '│ ')" tree_list "$f" "$new_prefix" else echo "${prefix}$([ "$is_last" = 1 ] && echo '└──' || echo '├──') $name" fi i=$(( i + 1 )) done } tree_list /path/to/dir

八、正则表达式

8.1 基本正则 vs 扩展正则

正则表达式有两种标准:基本正则表达式(BRE)和扩展正则表达式(ERE)。它们的区别在于一些元字符是否需要反斜杠转义。

元字符基本正则 (BRE)扩展正则 (ERE)
?(零次或一次)不支持(或 \??
+(一次或多次)不支持(或 \++
{n,m}(区间量词)\{n,m\}{n,m}
()(分组)\(\)()
|(逻辑或)不支持(或 \||
实用建议:在 grep 中使用 -E 启用扩展正则,在 sed 中使用 -E 启用扩展正则。在脚本中使用 ERE 能减少转义字符,提高可读性。

8.2 元字符详解

元字符含义示例匹配
.匹配任意单个字符(除换行符)c.tcat, cut, c0t
*前一个字符出现零次或多次ab*cac, abc, abbc
+前一个字符出现一次或多次 (ERE)ab+cabc, abbc (不匹配 ac)
?前一个字符出现零次或一次 (ERE)colou?rcolor, colour
^字符串开头^#以 # 开头的行
$字符串结尾\.py$以 .py 结尾的行
[]字符集,匹配其中任意一个[aeiou]任意元音字母
[^]否定字符集[^0-9]非数字字符
()分组 (ERE)(ab)+ab, abab, ababab
|逻辑或 (ERE)cat|dogcat 或 dog
{n,m}区间量词 (ERE)[0-9]{3,5}3 到 5 位数字
\b单词边界\bword\b匹配完整单词 word
\s空白字符(空格、Tab)
\d数字(等价于 [0-9])

8.3 grep/egrep 正则示例

# 使用扩展正则(grep -E) grep -E '^[A-Z]' file.txt # 以大写字母开头的行 grep -E '\.(jpg|png|gif)$' files # 图片文件扩展名 grep -E '^[0-9]{3}-[0-9]{4}' data # 电话号码格式 xxx-xxxx grep -E '^(#|$)' config.ini # 注释行和空行 grep -E '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' file # IP 地址 # 邮箱地址匹配 grep -E '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b' emails.txt # URL 匹配 grep -E 'https?://[^[:space:]]+' file.txt # 使用 grep -P(Perl 正则,GNU 扩展) grep -P '\d{4}-\d{2}-\d{2}' log.txt # 日期格式 YYYY-MM-DD grep -P '(?<!\d)123(?!\d)' file.txt # 负向零宽断言

8.4 sed 正则替换

# sed 替换中使用正则 sed -E 's/[[:space:]]+$//' file.txt # 删除行尾空白 sed -E 's/^[[:space:]]+//' file.txt # 删除行首空白 sed -E 's/([0-9]{3})-([0-9]{4})/\1****/' # 掩码电话号码 sed -E 's/^(.*)\.(.*)$/\2.\1/' # 交换小数点前后部分 sed -E '/^#/d; /^$/d' config.ini # 删除注释和空行 # 反向引用替换 echo "hello world" | sed -E 's/(\w+) (\w+)/\2 \1/' # world hello

8.5 实用正则模式集锦

模式说明正则
IP 地址IPv4 地址\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b
Email简单邮箱验证\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b
URLHTTP/HTTPS 链接https?://[^\s"'<>]+
日期YYYY-MM-DD\b\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\b
中文中文字符[\x{4e00}-\x{9fff}] (PCRE) 或 [一-龥]
十六进制颜色#RGB 或 #RRGGBB#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b
正整数正整数^[1-9][0-9]*$
浮点数带小数位数^-?[0-9]+(\.[0-9]+)?$

九、Shell 环境配置

9.1 配置文件加载顺序

Shell 在启动时会读取一系列配置文件。理解加载顺序对正确配置环境至关重要:

Bash 配置文件加载顺序

登录 Shell(如通过 SSH 登录、终端登录):

  1. /etc/profile(所有用户的全局配置)
  2. /etc/bash.bashrc(部分发行版)
  3. ~/.bash_profile(优先)或 ~/.bash_login~/.profile

非登录交互式 Shell(如在桌面打开终端、运行 bash):

  1. /etc/bash.bashrc(部分发行版)
  2. ~/.bashrc

非交互式 Shell(如运行脚本):

  1. 读取 $BASH_ENV 指定的文件

最佳实践

将环境变量写在 ~/.bash_profile(因为它是登录 Shell 执行的,Graphical 模式下终端也通常会 source 它)。
将别名和函数写在 ~/.bashrc(因为所有交互式 Shell 都会读取它)。
~/.bash_profile 中添加以下内容以确保登录 Shell 也会加载 ~/.bashrc

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

9.2 PATH 环境变量管理

PATH 变量定义了 Shell 在哪些目录中查找可执行文件。目录之间用 : 分隔。

# 查看当前 PATH echo "$PATH" # 添加目录到 PATH(临时,仅当前会话有效) export PATH="$HOME/bin:$PATH" # 添加到前面(优先级高) export PATH="$PATH:$HOME/bin" # 添加到后面(优先级低) # 永久添加(写入配置文件) echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # 防止重复添加 if [[ ":$PATH:" != *":$HOME/bin:"* ]]; then export PATH="$HOME/bin:$PATH" fi # 使用函数安全添加(推荐) path_add() { if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then export PATH="$1:$PATH" fi } path_add "$HOME/.local/bin"

9.3 别名 alias 设置

# 常用别名示例 # 安全操作 alias rm='rm -i' # 删除前确认 alias cp='cp -i' # 覆盖前确认 alias mv='mv -i' # 移动前确认 # 快捷操作 alias ll='ls -lah' # 详细列表 alias la='ls -A' # 显示所有文件(不含 . 和 ..) alias lt='ls -ltr' # 按时间排序(最新的在最后) # 便捷导航 alias ..='cd ..' alias ...='cd ../..' alias ....='cd ../../..' alias home='cd ~' # 系统操作 alias grep='grep --color=auto' # 高亮匹配 alias df='df -h' # 人类可读的磁盘空间 alias free='free -h' # 人类可读的内存信息 alias ps='ps auxf' # 进程树 # Git 别名 alias gst='git status' alias gl='git log --oneline --graph --all' alias gd='git diff' alias gp='git push' # 查看所有别名 alias -p # 临时跳过别名 \rm file.txt # 在命令前加反斜杠,使用原始命令 /bin/rm file.txt # 使用完整路径,跳过别名 unalias rm # 临时取消别名

9.4 提示符 PS1 定制

PS1 是 Shell 的主提示符,可以通过设置特殊转义序列来显示各种信息:

转义序列含义
\u当前用户名
\h主机名(短格式)
\H完整主机名
\w当前工作目录(完整路径)
\W当前工作目录(仅最后部分)
\d当前日期
\t当前时间(24小时 HH:MM:SS)
\T当前时间(12小时)
\@当前时间(12小时 AM/PM)
\n换行
\\$普通用户显示 $,root 显示 #
\[...\]包裹不可见字符(如颜色代码)
# 简单提示符 export PS1='\u@\h:\w\$ ' # 带颜色的提示符(Linux 风格) export PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' # 多行提示符(适用于长路径) export PS1='\u@\h:\w\n\$ ' # 带 Git 分支信息的提示符(需要 __git_ps1 函数) if [ -f /usr/share/git/git-prompt.sh ]; then . /usr/share/git/git-prompt.sh export GIT_PS1_SHOWDIRTYSTATE=1 export PS1='\u@\h:\w$(__git_ps1 " (%s)")\$ ' fi

9.5 历史命令管理

# 查看历史记录 history # 显示所有历史 history 10 # 显示最近 10 条 !123 # 执行历史中第 123 条命令 !! # 执行上一条命令 !$ # 上一条命令的最后一个参数 !grep # 执行最近以 grep 开头的命令 ^old^new # 编辑上一条命令并执行 # 搜索历史(Ctrl + R) # 在提示符下按 Ctrl + R,然后输入关键词搜索 # Bash 历史配置(在 .bashrc 中) export HISTSIZE=10000 # 内存中保留的命令数 export HISTFILESIZE=20000 # 历史文件中保留的命令数 export HISTFILE="$HOME/.bash_history" # 历史文件位置 export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S " # 显示时间戳 export HISTCONTROL=ignoredups:erasedups # 忽略连续重复命令 export HISTIGNORE="ls:cd:exit:pwd:clear" # 忽略列表中的命令 # 避免多个终端历史混乱 shopt -s histappend # 追加模式而不是覆盖 export PROMPT_COMMAND="history -a; $PROMPT_COMMAND" # 每个命令后立即写入

9.6 任务控制

# 后台运行 sleep 100 & # & 将命令放入后台 # 管理后台任务 jobs # 列出后台任务 jobs -l # 列出后台任务(含 PID) fg %1 # 将任务 1 调到前台 bg %1 # 将任务 1 放到后台继续运行 # 暂停与恢复 # Ctrl + Z -- 暂停前台任务 # bg -- 在后台继续运行被暂停的任务 # 终端关闭后继续运行 nohup long_running_task & # 忽略 SIGHUP 信号 disown %1 # 从任务表中移除(关闭终端也不会终止) # 终端复用工具(推荐) # tmux new -s session_name # 创建新 tmux 会话 # screen -S session_name # 创建新 screen 会话

9.7 实用 ~/.bashrc 配置模板

#!/usr/bin/env bash # ~/.bashrc -- 交互式 Bash Shell 配置 # 如果 ~/.bash_aliases 存在,加载它 if [ -f ~/.bash_aliases ]; then . ~/.bash_aliases fi # PATH 设置 export PATH="$HOME/.local/bin:$HOME/bin:$PATH" # 编辑器设置 export EDITOR=vim export VISUAL=vim # 语言环境 export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 # 历史设置 export HISTSIZE=10000 export HISTFILESIZE=20000 export HISTTIMEFORMAT="%F %T " export HISTCONTROL=ignoredups:erasedups export HISTIGNORE="ls:cd:exit:pwd:clear:history" shopt -s histappend # 每个命令后立即追加到历史文件 export PROMPT_COMMAND="history -a;$PROMPT_COMMAND" # 提示符(带颜色) PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' # 常用别名 alias ll='ls -lah' alias la='ls -A' alias lt='ls -ltr' alias grep='grep --color=auto' alias ..='cd ..' alias ...='cd ../..' # 安全操作 alias rm='rm -i' alias cp='cp -i' alias mv='mv -i' # 函数:mkcd -- 创建目录并进入 mkcd() { mkdir -p "$1" && cd "$1" } # 函数:extract -- 自动解压各种格式 extract() { if [ -f "$1" ]; then case "$1" in *.tar.bz2) tar xjf "$1" ;; *.tar.gz) tar xzf "$1" ;; *.bz2) bunzip2 "$1" ;; *.rar) unrar e "$1" ;; *.gz) gunzip "$1" ;; *.tar) tar xf "$1" ;; *.tbz2) tar xjf "$1" ;; *.tgz) tar xzf "$1" ;; *.zip) unzip "$1" ;; *.Z) uncompress "$1" ;; *.7z) 7z x "$1" ;; *) echo "无法解压 '$1' - 未知格式" ;; esac else echo "'$1' 不是有效文件" fi } # 补全增强 if [ -f /usr/share/bash-completion/bash_completion ]; then . /usr/share/bash-completion/bash_completion fi

配置文件修改后生效

修改 .bashrc.bash_profile 后,需要重新加载才能生效:

  • source ~/.bashrc. ~/.bashrc
  • exec bash -- 重新启动 Shell
  • 关闭并重新打开终端

十、Shell 调试与最佳实践

10.1 调试模式

Shell 脚本提供了多种调试方式,帮助定位问题:

调试方法说明
bash -x script.sh在执行前显示每条命令(带 + 前缀)
bash -n script.sh只检查语法错误,不执行
bash -v script.sh显示输入的原始命令
set -x / set +x脚本中启用/禁用调试输出
set -e任何命令失败时立即退出(errexit)
set -u使用未定义变量时报错(nounset)
set -o pipefail管道中任何命令失败都视为管道失败
# 脚本内置调试 #!/usr/bin/env bash set -e # 遇到错误即退出 set -u # 未定义变量报错 set -o pipefail # 管道错误传递 # set -x #(调试时取消注释) # 或者在脚本中局部启用调试 echo "调试开始" set -x # 需要调试的代码块 result=$(some_complex_command) set +x echo "调试结束, result=$result" # 使用 DEBUG trap 逐行跟踪 trap 'echo "行 $LINENO: $BASH_COMMAND"' DEBUG

10.2 错误处理

# 检查命令执行结果 if ! mkdir -p /some/directory; then echo "创建目录失败" >&2 exit 1 fi # 使用 trap 捕获错误和退出信号 cleanup() { echo "清理临时文件..." rm -rf /tmp/myscript_* 2>/dev/null || true } # 注册清理函数,确保脚本退出时执行 trap cleanup EXIT # 捕获特定信号 trap 'echo "被中断"; exit 1' INT TERM trap 'echo "发生错误,行 $LINENO"' ERR # 错误处理函数封装 die() { echo "[错误] $*" >&2 exit 1 } require_cmd() { if ! command -v "$1" &>/dev/null; then die "需要 $1 命令但未找到,请先安装" fi } require_cmd "curl" require_cmd "jq"

10.3 安全编程实践

Shell 安全编程要点

  • 始终引用变量:使用 "$var" 而不是 $var,避免空格导致的拆分
  • 使用 set -euo pipefail 作为脚本头部
  • 检查参数:总是在使用前验证输入参数
  • 注意文件名中的特殊字符:使用 find -print0 | xargs -0 模式
  • 临时文件使用 mktemp 而不是硬编码路径
  • 避免 eval:eval 执行任意代码,尽量避免使用
# 安全编程示例 #!/usr/bin/env bash set -euo pipefail # 参数检查 if [ "$#" -lt 1" ]; then echo "用法: $0 <文件名>" >&2 exit 1 fi input_file="$1" # 检查文件是否存在 [ -f "$input_file" ] || die "文件 '$input_file' 不存在" # 安全创建临时文件 tmpfile=$(mktemp /tmp/process_XXXXXX) trap 'rm -f "$tmpfile"' EXIT # 处理文件 grep -E '^[0-9]+' "$input_file" > "$tmpfile" mv "$tmpfile" "$input_file.processed" echo "处理完成,已保存到 $input_file.processed"

10.4 性能优化建议

建议说明原因
减少外部命令调用优先使用 Bash 内置功能外部命令(如 grep、sed)需要 fork 进程,开销大
使用 $(( )) 替代 expr内置算术运算expr 是外部命令,$(( )) 是内置功能
使用 [[ ]] 替代 [ ]Bash 关键字而非外部命令[[ ]] 是 shell 语法,[ ] 对应 test 命令
避免在循环中创建子进程如避免 for i in $(cat file)命令替换会创建子进程,大文件时内存消耗严重
使用 printf 替代 echoprintf 更可靠echo 在不同平台行为不一致
避免过度使用管道考虑使用 awk 代替多个 grep/sed 组合每个管道阶段创建新进程
# 性能对比 # 慢版本 for word in $(cat words.txt); do echo "$word" done # 快版本(避免命令替换和单词拆分) while IFS= read -r word; do echo "$word" done < words.txt # 使用 awk 一行代替多个命令 # 等价于: cat file | grep pattern | cut -d: -f2 | sort | uniq -c | sort -rn awk -F: '/pattern/ { count[$2]++ } END { for (k in count) print count[k], k }' file \ | sort -rn

10.5 常见错误与陷阱

陷阱错误示例正确写法
变量赋值等号周围加空格name = "John"name="John"
if 条件中缺少空格if ["$x" = "$y"]if [ "$x" = "$y" ]
忘记引用变量[ -f $file ][ -f "$file" ]
test 中使用 <>[ "$a" > "$b" ][ "$a" -gt "$b" ](( a > b ))
for 循环中使用 $(cat file)for i in $(cat file)while read -r i; do ... done < file
忘记在 case 中使用 ;;case $x in a) echo;; esac每个分支以 ;; 结束
shebang 中错误路径#!/bin/bash(在非标准Linux)#!/usr/bin/env bash
使用 echo 处理选项echo "-n"echo "hello\nworld"使用 printf

10.6 ShellCheck 工具

ShellCheck 是一个 Shell 脚本静态分析工具,可以检测脚本中的常见错误、陷阱和不良实践。它被集成到许多编辑器和 CI 工具中。

安装 ShellCheck

  • Linux:apt install shellcheckyum install shellcheck
  • macOS:brew install shellcheck
  • Windows:通过 WSL 安装,或在 VS Code 中安装 shellcheck 扩展
  • 在线:访问 shellcheck.net 粘贴代码分析
# 使用 ShellCheck 分析脚本 shellcheck myscript.sh # 指定 Shell 类型 shellcheck --shell=bash myscript.sh shellcheck --shell=sh myscript.sh # POSIX 模式 # 输出格式 shellcheck -f json myscript.sh > report.json shellcheck -f gcc myscript.sh # GCC 风格输出(与编辑器集成) # 在 CI 中集成 # 在 .github/workflows/check.yml 中添加: # - name: Run ShellCheck # run: shellcheck --shell=bash --external-sources *.sh
ShellCheck 常见警告代码:
  • SC2086 -- 变量引用未加双引号(最常见的错误)
  • SC2002 -- 无用的 cat(如 cat file | grep 改为 grep file
  • SC2046 -- 未加引号的命令替换(单词拆分问题)
  • SC2162 -- read 命令缺少 -r 参数
  • SC1091 -- 无法找到 source 的文件

十一、与 Claude Code 的结合

11.1 Claude Code 中 Shell 的使用场景

Claude Code 作为一个命令行 AI 编程助手,深度依赖 Shell 环境来完成各种任务。以下是 Shell 在 Claude Code 中的主要使用场景:

场景说明示例
文件操作创建、读取、编辑、移动文件Read file.txt, Write output.txt
命令执行运行 Shell 命令完成任务Bash: git status, Bash: npm test
文本搜索在项目中搜索代码和文本使用 Grep 工具在代码库中搜索
代码分析统计代码行数、查找模式等find . -name "*.py" | xargs wc -l
构建部署运行构建脚本、部署命令Bash: make build && ./deploy.sh
Git 操作版本控制操作Bash: git diff, git commit
环境配置安装软件、配置环境Bash: pip install -r requirements.txt
Claude Code 中的命令执行机制:Claude Code 通过 Bash 工具执行 Shell 命令。每次 Bash 调用都在一个隔离的环境中运行,工作目录会重置到项目根目录。Shell 状态(如变量、环境设置)不会在命令之间持久化,因此每个命令应该是自包含的。

11.2 在 Claude Code 对话中执行 Shell 命令

Claude Code 通过内置的 Bash 工具执行 Shell 命令。使用场景包括:

# 在 Claude Code 中可以执行以下类型的 Shell 命令: # 1. 文件系统操作 ls -la src/ mkdir -p dist/assets cp -r public/* build/ # 2. 项目构建和测试 npm run build python -m pytest tests/ cargo test -- --nocapture # 3. 代码分析 cloc --exclude-dir=node_modules . wc -l src/**/*.ts grep -rn "deprecated" --include="*.py" . # 4. Git 操作 git log --oneline -10 git diff --stat git status # 5. 包管理 npm install lodash pip install flask go get github.com/gin-gonic/gin

安全注意事项

Claude Code 在执行 Shell 命令时遵循安全策略。某些命令可能需要用户确认授权:

  • 文件修改操作通常需要权限确认
  • 需授权后才能执行危险命令(如 rm -rf
  • 可以通过 update-config 技能配置权限白名单
  • 建议将可靠的只读命令添加到 allowlist 以减少权限提示

11.3 Bash 工具在文件操作中的应用

Claude Code 的 Bash 工具在文件操作中发挥着重要作用。以下是一些在 Claude Code 工作流中常用的模式:

# 探索项目结构 ls -la # 查看当前目录内容 find . -name "*.config.js" # 查找配置文件 tree -L 2 src/ # 查看目录树(2层深度) # 文件内容搜索 grep -rn "import" src/ --include="*.ts" # 搜索所有 import 语句 grep -rn "TODO\|FIXME" . --exclude-dir=node_modules # 查找待办事项 # 文件批量重命名 for f in *.js; do mv "$f" "${f%.js}.mjs"; done # .js → .mjs # 统计信息 find src/ -name "*.py" | xargs wc -l | tail -1 # Python 总代码行数 du -sh dist/ # 构建产物大小

在 Claude Code 中高效使用 Shell

  • 描述性参数:使用 description 参数描述每个 Bash 命令的用途,方便阅读日志
  • 后台任务:长时间运行的任务可以使用 run_in_background 参数,Claude Code 会在任务完成时通知
  • 超时设置:长时间运行的任务可以设置 timeout 参数(最长 10 分钟)
  • 分步执行:复杂的任务应该分解为多个 Bash 调用,而不是塞入一个长命令中

11.4 实用提示词模板

以下是一些在 Claude Code 中使用 Shell 的实用提示词模板:

提示词模板集锦

  • 代码搜索:"在 src/ 目录下搜索所有包含 'useEffect' 的 .tsx 文件,显示文件名和行号"
  • 批量操作:"将所有 .jpg 文件转换为 .webp 格式,保留原文件"
  • 数据统计:"统计项目中的代码行数,按文件类型分组"
  • 日志分析:"分析 app.log,找出最近 1 小时内的所有 ERROR 级别日志"
  • 环境检查:"检查当前系统中 Node.js、Python、Git 的版本"
  • 文件比较:"比较 dist/ 和 src/ 两个目录的差异"
  • 项目初始化:"创建一个新的 React 项目,包含 TypeScript 和 ESLint 配置"
# Claude Code Shell 使用示例:分析项目健康度 # 提示词:"分析这个项目的健康状况" # Claude Code 可能会执行: # 1. 检查项目结构 ls -la cat package.json 2>/dev/null || cat Cargo.toml 2>/dev/null || ls *.py # 2. 检查依赖状态 npm outdated 2>/dev/null || cargo outdated 2>/dev/null || pip list --outdated 2>/dev/null # 3. 运行测试 npm test 2>&1 | tail -20 || cargo test 2>&1 | tail -20 || python -m pytest 2>&1 | tail -20 # 4. 检查代码质量问题 grep -rn "TODO\|FIXME\|HACK\|XXX" src/ --include="*.{js,ts,py,rs}" 2>/dev/null | head -20

十二、核心要点总结

Shell 学习路线图

掌握 Shell 编程是一个循序渐进的过程,以下是建议的学习路径:

基础命令 文件操作 文本处理 管道重定向 脚本编写 流程控制 正则表达式 环境配置 高级技巧

12.1 每日必用命令速查

分类命令一句话说明
导航pwd显示当前路径
ls -la列出所有文件(含隐藏)的详细信息
cd -切换到上一个目录
文件cp -r src/ dest/递归复制目录
mv old new重命名或移动
rm -rf dir/递归强制删除(慎用!)
ln -s target link创建符号链接
查看cat file显示文件全部内容
less file分页浏览(支持搜索)
tail -f log实时监控日志文件
head -n 20 file显示前 20 行
文本grep -rn "pat" .递归搜索模式
sed -i 's/old/new/g' file全局替换并原地修改
awk '{print $1}' file打印第一列
sort \| uniq -c排序并统计频率
系统ps aux \| grep nginx查找特定进程
chmod +x file添加执行权限
df -h查看磁盘使用情况

12.2 三大黄金原则

原则一:引用所有变量

始终使用 "$var" 而不是 $var。即使 99% 的情况下不加引号也能工作,那 1% 的边界情况会让你追查数小时。使用双引号可以防止单词拆分和通配符展开。

原则二:启动脚本使用 set -euo pipefail

这是 Bash 脚本的"安全模式":

  • -e:命令失败时立即退出,避免错误蔓延
  • -u:使用未定义变量时报错,捕获拼写错误
  • -o pipefail:管道中任一步骤失败就视为整体失败

仅需 set -euo pipefail 这一行,就能避免 80% 的 Shell 脚本常见错误。

原则三:每个工具做好一件事,通过管道组合

Unix 哲学的核心:grep 搜索文本、sed 编辑文本、awk 分析文本、sort 排序。将它们通过管道组合起来,就能构建出强大的数据处理流水线。不要试图用单个工具完成所有事情。

12.3 命令速查组合模式

# 模式 1:文件搜索 + 过滤 + 处理 find . -name "*.log" -mtime -7 | xargs grep -l "ERROR" | xargs rm # 解释:找到 7 天内修改的 .log 文件,过滤出包含 ERROR 的,删除 # 模式 2:数据统计 + 排序 + 截取 awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10 # 解释:提取访问日志的 IP,去重统计,按次数倒序,取前 10 # 模式 3:备份 + 修改 + 验证 cp config.ini config.ini.bak && sed -i 's/debug=false/debug=true/' config.ini && diff config.ini config.ini.bak # 解释:备份配置文件,修改,用 diff 验证修改结果 # 模式 4:遍历 + 并行处理 for f in *.jpg; do convert "$f" -resize 50% "thumb_$f" & done; wait # 解释:并行创建所有图片的缩略图(& 放入后台),wait 等待全部完成

12.4 学习资源推荐

资源类型说明
GNU Bash 手册官方文档Bash 的权威参考手册
ShellCheck工具Shell 脚本静态分析,帮助避免常见错误
ExplainShell网站逐段解释 Shell 命令的每个部分
Linux Man Pages文档Linux 系统调用的完整手册
Oh My Zsh框架Zsh 的插件和主题管理框架
tldr pages工具/网站命令的简明示例(比 man 更易读)

最终寄语

Shell 是开发者的基本功,也是生产力的倍增器。它可能看起来语法古怪、陷阱众多,但一旦掌握,你将获得对计算机系统的深度掌控能力。每一个高手都是从第一条命令开始积累的。每天多用、多查、多练,Shell 会成为你最得力的工具。

记住:在 Shell 的世界里,没有什么是几个管道不能解决的。如果不行,就再加一个。

27377