2021 年已经不太远了。还好拜登赢得了美国大选,地球终于可以换一口气了。

我们接着看自由软件。上次我们讲到了 BASH 中 的错误处理。这次我们来看看几个初学者容易犯的 BASH 错误——如果不注意,这些错误的后 果可能很严重。

  • 你少给了 BASH 命令一个参数。

刚开始使用 BASH 的朋友可能还不习惯 unix 的沉默——许多命令没有显式返回,只是默默地 等待或者回到提示符;他们也可能不熟悉有的命令需要至少两个参数,比如 grep。如果 你只输入一个参数:

grep "gnu"

那么,你就可能陷入默默的漫长等待——grep 在等你输入第二个参数!正确的用法是

grep "gnu" foo.txt

或者

cat foo.txt | grep "gnu"

不知道这个就会让生命在等待中错过。

  • 不带引号的通配符

下面的 BASH 命令非常常见

find . -name *.c

你要找到当前目录及其子目录下的 c 文件——也许你要管理一下代码文件。该命令的返回可 能是

./src/main.c
./src/foo.c
./lib/bar.c

也许你想删除其他文件,只保留 c 文件。在删除其他文件之前,你想先确认一下有哪些不 是 c 的文件。于是,你输入命令

find . -not -name *.c

它告诉你

./readme.txt
./lib/other.cpp

你觉得没问题,可以删除。于是,你输入命令

find . -not -name *.c -delete

结果一切如你所想,剩下的文件是

./src/main.c
./src/foo.c
./lib/bar.c

换一种场景,你当前目录里的文件是

.
./readme.txt
./test.c
./lib
./lib/other.cpp
./src
./src/main.c
./src/foo.c
./lib/bar.c

当你执行同样的删除命令时

find -not -name *.c -delete

你的当前目录里就只剩下

.
/test.c

你辛辛苦苦的开发成果就付诸东流了——全删除了。这是因为 BASH 会先把通配符扩展成当前 能够匹配的文件,然后再执行命令。所以上面的命令实际变成了

find . -not -name test.c -delete

后果可想而知。正确的做法是使用单引号

find . -name '*.c'

这样,通配符就不会被解析扩展了。常见的命令lsgrepsedfindawk都会 碰到类似的问题,所以这个引号还是很重要的。

  • 奇妙的小数点.

你想搜索一下文件里的浮点数,就是带小数点.的数。于是,你测试以下命令

echo "1234.5678" | grep -Eo "[0-9]+.[0-9]+"

它返回

1234.5678

看来没什么问题。作为一个严谨的开发人员,你通常都至少要有 3 个测试用例,你又输入 命令

echo "1234A5678" | grep -Eo "[0-9]+.[0-9]+"

出乎你所料,它居然返回

1234A5678

你莫名其妙。其实,这是因为.是一个通配符,它可以替代除了换行之外的任意字符。所 以,正确的命令是不解析它(使用\

echo "1234A5678" | grep -Eo "[0-9]+\.[0-9]+"

结果正常了,是空

  • 小心反单引号``

你有一个文件 foo.txt,其内容是

The usage of `ls`
ls -a
ls -l

你想搜索其中的 ls,没问题,你在 BASH 中输入命令

grep ls foo.txt

结果正常

The usage of `ls`
ls -a
ls -l

你还想搜索带反引号的ls,于是你输入命令

grep `ls` foo.txt

结果输出一堆你始料不及的结果。你觉得可能是要加双引号,于是输入

grep "`ls`" foo.txt

结果什么也没出来。什么原因?因为 BASH 是会解析反引号的——结果是把反引号里的命令结 果输入。如果要避免命令解析,你需要使用单引号把命令引起来

grep '`ls`' foo.txt

结果正常了

The usage of `ls`

BASH 的用法很奇妙,多做测试(至少有三个测试用例)有助于你深刻记忆这些细微之处, 避免严重错误。