BASH 的几个常见陷阱
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'
这样,通配符就不会被解析扩展了。常见的命令ls
、grep
、sed
、find
、awk
都会
碰到类似的问题,所以这个引号还是很重要的。
- 奇妙的小数点
.
你想搜索一下文件里的浮点数,就是带小数点.
的数。于是,你测试以下命令
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 的用法很奇妙,多做测试(至少有三个测试用例)有助于你深刻记忆这些细微之处, 避免严重错误。