许久之前,师弟问了我一个问题,为什么shell中添加环境变量的写法是下面这种方式
```bash
PATH=~/.lib:$PATH; export PATH
```
而下面这种会报错呢?
```bash
$PATH=~/.lib:$PATH; export PATH
```
当时我的回答是,"shell就是这样子规定的呀"。 回答的同时,也突然间发现有些自己感觉很熟悉的概念,原来自己并没有那么清楚,因此这一篇讲讲shell的命令行替换。先说结论
> shell会在命令执行前对命令行进行一些**替换**
shell替换有如下几种:
| 替换 | 语法 | 含义|
| ---- | ----| ----|
| 历史| ! | 之前使用命令|
|大括号| {} | 指定的文本|
|代字号| ~username | 用户的主目录|
| 变量| \$, \${...} | shell和环境变量|
|算术| \$((..))| 整数运算|
|命令替换| \`...\`, \$(...)| 运行在子shell里命令的输出|
|路径名| \*,?,[...],[^...]| 文件系统中匹配的文件名|
**历史替换**是以`!`开头的替换方式,以下面历史记录为例

```bash
$ !! # 执行上一个命令,即history
$ !1021 # 支持第1021个命令 即ls
$ !-2 # 执行倒数第二个命令,即ls
```
**大括号替换**: 它会将{...}里的内容展开为多个单词,可以快速创建有一定规律的文件. 下面这个命令就把"chap0{1..3}"替换成了chap01, chap02, chap03, 以及每个都还有一个html和text对应。
```bash
$ mkdir -p chap0{1..3}/{html,text}
$ tree chap0*
chap01
├── html
└── text
chap02
├── html
└── text
chap03
├── html
└── text
```
**代字号代替**: 我们经常会看到别人文章会写用`vim ~/.bashrc`修改家目录下的配置文件,其中`~`默认就会替代成自己家目录路径,可以用`echo ~`确认。
那么问题来了,如何我想快速到别人的家目录下,应该怎么操作。只要在`~`加上别人的用户名就行了。比如说我/home 下还有一个用户叫做abc, 那么查看它家目录下的内容就是
```bash
ls ~abc
```
注: `~a`可以用tab补全成`~abc`哦
**变量替换**: shell会把`${变量名}`或者`$变量名`替换成变量所指代的具体字符,比如说我将abc指代为`ls`,那么shell就会将`$abc`解释成`ls`,然后执行`ls`
```bash
abc=ls
$abs
# Desktop bin biosoft blastdb miniconda3 ncbi
```
也就是`$PATH=~/.lib:$PATH; export PATH`报错的原因是,shell在执行命令前会把`$PATH`成原来PATH里的字符串,显然无法达到修改`PATH`的目的
**算术替换**: shell命令行支持整数型的数学运算,下面的运算都是可以的,但是就别拿100/2.5这种浮点运算为难shell了。
```bash
echo $((1+2))
echo $((1-2))
echo $((100*101))
echo $((100/50))
```
**命令替换**:这个替换非常的实用,可以将shell命令的输入结果立刻作为输入,而不是额外创建一个变量命。有一个应用场景就是在的分析报告里加上完成时间点
```bash
touch reports.$(date +%d%b%Y).log
```
**路径名替换**:路径替换的语法就4种,`*`表示0或更多的任意字符,`?`表示一个任意字符,`[...]`表示括号内的字符之一,`[^...]`不包括括号内的字符
以上就是shell命令行替换的几种形式。当然为了再一次强调"shell会在命令执行前对命令行进行一些**替换**",下面举一个反面例子来说明下。
Linux的`/etc`目录下有很多以`conf`结尾的配置文件,我们可以用find命令快速的定位到它们。
```bash
find /etc -name *.conf
```
上面的命令看起来没啥毛病,但是只要多做一件事情,就会有报错哦
```bash
touch a.conf b.conf
find /etc -name *.conf
# 如下是报错
find: paths must precede expression: b.conf
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec|time] [path...] [expression]
```
你会不会好奇,明明是相同的命令,却又不同的境遇呢?让我解释下,在刚开始的时候,文件下面没有"a.conf","b.conf",尽管shell看到"*"会有一种进行通配的冲动,但是很可惜没有对象让它统配。后来我们创建了这两个文件,给shell找到通配的机会,于是实际执行的命令就成了 "find /etc -name a.conf b.conf"。 由于后面这两个是文件路径,不符合find的命令要求,就导致了报错。
>其实报错还好,有些时候没有报错,程序运行得到错误的结果反而更惨
如何避免这种错误呢?我们就需要用到`"`避免`*`这个元字符被shell解释。
除了双引号,避免shell进行替换的符号还有 反斜杠\ , 和单引号 '. 单引号和双引号的区别在于,单引号内部所有字符都是普通字符而已,而双引号里的美元符号\$, 感叹号! 和反引号 ` 还能被shell解释
从PATH说起的shell命令行替换