1. Bash 展开机制的整体顺序
Bash 在处理命令行时,会按固定顺序执行多种展开(Expansions)。这一顺序至关重要,因为不同展开会相互影响。
| 步骤 | 展开类型 | 执行时机 | 关键说明 |
|---|---|---|---|
| 1 | Brace Expansion(大括号展开) | 最先执行 | {a,b} → a b;支持序列 {1..10} |
| 2 | Tilde Expansion (波浪号展开) Parameter/Variable Expansion (参数/变量展开) Arithmetic Expansion (算术展开) Command Substitution(命令替换) | 同时执行,从左到右扫描 | ~、$(...)、${var}、$((...)) 等在此阶段处理 |
| - | Process Substitution(进程替换) | 与步骤 2 同时 | <(...)、>(...) |
| 3 | Word Splitting(单词分割) | 步骤 2 完成后 | 根据 $IFS 分割单词(默认空格、Tab、换行) |
| 4 | Filename Expansion(路径名展开/Globbing) | 步骤 3 完成后 | * ? [] 通配符匹配文件名 |
| 5 | Quote Removal(引号移除) | 最后执行 | 移除未转义的 \' " \ |
注意:
- 双引号内抑制单词分割和路径名展开。
- 单引号内抑制所有展开。
- 引号移除总是最后一步,仅移除原始输入中的引号(展开产生的引号不移除,除非被转义)。
2. 主要展开类型详解
2.1 波浪号展开(Tilde Expansion)
在单词开头或赋值语句中,未加引号的 ~ 会展开为主目录路径。
| 形式 | 展开结果 | 示例 |
|---|---|---|
~ |
当前用户主目录($HOME) |
cd ~ → 回家目录 |
~username |
指定用户主目录(从 /etc/passwd 查询) |
ls ~root |
~+ |
当前工作目录($PWD) |
echo ~+ |
~- |
上一个工作目录($OLDPWD) |
cd ~-(等价于 cd -) |
~N(N 为数字) |
目录栈第 N 项(dirs 显示) |
cd ~2 |
~/path |
主目录下路径 | cp file ~/backup |
- 与
cd -结合:cd -切换到上一个目录并打印路径(使用$OLDPWD)。 - 展开结果被视为已加引号,不会进一步分割或 glob。
2.2 参数与特殊参数展开
| 特殊参数 | 含义 | 示例 |
|---|---|---|
$_ |
上一个前台管道或命令的最后一个参数(执行后设置) | echo hello world → echo $_ 输出 world |
!$ |
通过历史展开,等价于上一个命令的最后一个参数 | sudo !!$(历史展开形式) |
!! |
通过历史展开,等价于上一个完整命令 | sudo !! |
| Alt + .(或 Esc + .) | Readline 快捷键:插入上一个命令的最后一个参数(等价 $_) |
其他常见特殊参数:
$?:上一个命令退出状态$$:当前 Shell PID$#:位置参数个数$@/$*:所有位置参数
2.3 历史展开(History Expansion)
默认在交互式 Shell 中启用(set -H 控制),以 ! 开头,在读取完整行后立即执行。
| 形式 | 含义 | 示例 |
|---|---|---|
!! |
上一个完整命令 | sudo !! |
!n |
历史第 n 条命令 | !100 |
!-n |
倒数第 n 条(!-1 = !!) |
!-2 |
!string |
最近以 string 开头的命令 | !ls |
!?string? |
最近包含 string 的命令 | |
!^ |
上一个命令第一个参数 | |
!$ |
上一个命令最后一个参数(同 $_ 在某些场景) |
mv !$ backup/ |
!* |
上一个命令所有参数 | |
!!:gs/old/new/ |
上一个命令全局替换 old 为 new | !!:gs/typo/correct/ |
:p |
仅打印展开结果,不执行 | !ls:p |
^old^new^ |
快速替换(非全局) | ^cat^dog^ |
- 建议启用
shopt -s histverify:展开后先显示,让你二次确认。 - 在脚本中默认禁用,可用
set -H开启。
2.4 其他展开简述
- Brace Expansion :
{a..z}、pre{a,b}post - Command Substitution :
$(command)或command - Arithmetic Expansion :
$((1+2)) - Globbing :
* ? [],可通过shopt -s extglob启用扩展模式?(pat)、*(pat)等
3. Tab 补全机制(Programmable Completion)
Tab 补全是 Bash 最强大的交互工具,由 Readline 库实现,支持可编程补全。
基本行为
- 单 Tab:唯一匹配时直接补全。
- 双 Tab:列出所有可能匹配。
- 默认补全:命令、文件名、变量、用户名、主机名等。
可编程补全机制
-
使用
complete内置命令定义 compspec。 -
核心变量:
COMPREPLY(数组,存放补全结果)。 -
辅助工具:
compgen(生成匹配列表)。 -
常见选项:
-f/-d:文件名/目录-c:命令-v:变量-u:用户名-A function:调用自定义函数
-
示例自定义补全函数:
bash_myfunc() { COMPREPLY=( $(compgen -W "opt1 opt2 opt3" -- "$2") ) } complete -F _myfunc mycommand
快捷键(Readline 默认绑定)
| 快捷键 | 功能 |
|---|---|
| Tab | 补全 |
| M-Tab | 菜单式补全(循环) |
| M-. / Alt+. | 插入上一个命令最后一个参数(等价 $_ / !$) |
| M-* | 插入所有匹配 |
| M-? | 列出所有匹配 |
| Ctrl-R | 反向历史搜索 |
- 许多命令(如 git、systemctl)自带复杂补全脚本(来自 bash-completion 包)。
4. 实用技巧与建议
cd -:快速切换上/下目录。Alt + .:快速引用上一个参数(比!$更方便)。Ctrl + R:历史反向搜索。shopt -s cdable_vars:允许cd varname直接进入变量路径。- 启用
shopt -s direxpand:补全时直接展开变量。 - 查看当前补全:
complete -p <command>。
这些机制相互配合,让 Bash 命令行异常高效。熟练掌握展开顺序和历史/补全技巧,能显著减少重复输入。更多细节可以参考官方手册 man bash 中的 EXPANSION 、HISTORY EXPANSION 和 PROGRAMMABLE COMPLETION 章节。