当我们按下回车键时,命令行里到底发生了什么“魔术”?这一部分,我们会用一个新的,也是唯一的命令 echo 来揭开这一切背后的秘密。echo 的功能很简单,就是把你给它的东西再显示出来。
每次你在命令行输入文本并敲下回车,Bash 都会在执行命令前,先对你输入的内容做一些“手脚”。我们之前见过 * 这种神奇的符号,它对于 Shell 来说意义非凡。这种把一个简单的符号变成另一堆内容的过程,就叫做“扩展”(Expansion)。

扩展是Shell在执行命令前对输入内容进行预处理的过程。它会把特殊字符和模式转换成实际的值,然后再传递给命令执行。
扩展发生在命令执行之前,这意味着命令本身看到的已经是扩展后的结果,而不是你输入的原始内容。
让我们用 echo 来看看这个魔术。如果我们让它显示一段普通的文字,它会原样输出:
|$ echo this is a test this is a test
这很直接。但如果我们给它一个 * 呢?
|$ echo * Desktop Documents ls-output.txt Music Pictures Public Templates Videos
奇怪,* 怎么不见了?这正是“扩展”的魔力。当你按下回车时,Shell 会抢在 echo 命令之前,把 * 扩展成了当前目录下的所有文件名。
所以 echo 命令自己压根就没见到过 *,它见到的是 * 变身之后的结果,也就是一大串文件名,然后它忠实地把这些文件名给显示了出来。
这种因为 * 这类通配符而发生的扩展,有个专门的名字,叫“路径名扩展”。它就像你打牌时出一张“王牌”,这张牌可以代表任何一张你想要的牌。在 Shell 里,* 就代表了任何文件名。
类似的,D* 就会扩展成所有以 “D” 开头的文件名:
|$ echo D* Desktop Documents
还记得我们用 cd 命令时,那个波浪号 ~ 吗?它也是一种扩展,叫“Tilde 扩展”。~ 是个快捷方式,一个昵称,它会自动扩展成当前用户的家目录。这样你就不用每次都输入长长的 /home/your_name 了。
|$ echo ~ /home/me
Shell 还能当计算器用,这时我们就要用到“算术扩展”。你只需要把数学算式放进 $(()) 里,Shell 就会先算出结果,再交给外面的命令。
|$ echo $((2 + 2)) 4
它甚至能处理更复杂的运算,比如先算5的平方,再乘以3:
|$ echo $(((5**2) * 3)) 75
不过要注意,Shell 的算术扩展只懂整数。所以 5/2 的结果会是2,而不是2.5。
“大括号扩展”可能是最奇特的扩展了。它像一个模具,能让你根据一个简单的模式,创造出大量的文本。 比如,你想创建好几个文件夹,分别用A、B、C来命名:
|$ echo Front-{A,B,C}-Back Front-A-Back Front-B-Back Front-C-Back
这个功能在创建有规律的文件或目录时特别方便。比如,一个摄影师想整理照片,他可以这样做来创建从2007年到2009年,每个月份的文件夹:
|$ mkdir {2007..2009}-{01..12}
只用一行命令,36个文件夹就瞬间创建好了。
“命令替换”可以让你把一个命令的输出,当作另一个命令的输入。你只需要把第一个命令用 $() 包起来。
比如,你想知道 cp 命令到底藏在哪里,可以用 which cp 找到它的路径。然后,你可以把这个结果直接“喂”给 ls -l 命令来看它的详细信息:
|$ ls -l $(which cp) -rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp
Shell 会先执行 $(which cp),得到结果 /bin/cp,然后整个命令就变成了 ls -l /bin/cp。
既然 Shell 有这么多神奇的扩展,那如果我们不想要它自作主张地“变魔术”该怎么办呢?比如,我就想让 echo 显示一个 * 符号,而不是一堆文件名。
为了控制这些扩展,我们需要使用“引号”。引号就像是扩展功能的开关,可以精确地告诉 Shell 什么时候该“变魔术”,什么时候应该“保持原样”。
双引号 "" “是一个比较温柔"的开关。它会关闭大部分扩展,比如路径名扩展(* 不会变)和大括号扩展,但它会保留一些最有用的扩展,比如参数扩展($USER 依然会显示用户名)和命令替换($(cal) 依然会显示日历)。
这在处理带有空格的文件名时特别有用。没有引号,two words.txt 会被看成两个东西;有了双引号,"two words.txt" 就是一个完整的文件名。
我们看看双引号如何保留换行符,让日历正常显示:
|$ echo "$(cal)" 二月 2008 日 一 二 三 四 五 六 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
如果你想让所有特殊字符都“失效”,让它们只作为普通文本出现,那就用单引号 ''。单引号是一个“彻底”的开关,它会关闭一切扩展。所见即所得。
|$ echo 'text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER' text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
你看,所有东西都原样输出了。
有时候,我们只想关闭某一个字符的特殊功能,而不是一整段。这时可以用反斜杠 \,它被称为“转义字符”。它能让紧跟在它后面的那个特殊字符变成普通字符。
比如,你想在屏幕上显示 $ 符号,但又不希望它被当作变量扩展,就可以这样:
|$ echo “The balance for user $USER is: \$5.00” The balance for user me is: $5.00
$USER 被正常扩展了,但 \$5.00 里的 $ 就因为前面的 \ 而被当作了普通的美元符号。
理解扩展的执行顺序是非常重要的,因为它决定了命令在执行时的行为。想象一下,扩展就像是一个接力赛,每个扩展都有自己的顺序和规则,只有按照正确的顺序进行,才能确保最终的结果是我们期望的。 就像在厨房里做饭一样,先切菜再炒菜,顺序错了,味道就不对了。
{a,b,c}~$VAR$((...))$(...)*, ?, [...]“...”, '...'让我们来做一些练习,巩固刚才学到的知识:
|# 1. 路径名扩展 $ echo *.txt $ echo file[1-5].txt # 2. 波浪号扩展 $ echo ~ $ cd ~/Documents # 3. 算术扩展 $ echo $((10 + 5 * 2)) $ echo $((2 ** 10)) # 4. 大括号扩展 $ echo {1..5} $ mkdir