通常情况下,我们总是会结合使用`ps`和`grep`的方式来筛选出只包含某个进程名的那些行,比如: ```bash $ ps aux | grep nginx root 9926 0.0 0.1 23388 948 ? Ss Mar15 0:00 nginx: master process ./sbin/nginx nobody 21964 0.0 0.3 23388 1536 ? S Mar16 0:16 nginx: worker process nobody 21965 0.0 0.4 23388 2032 ? S Mar16 0:14 nginx: worker process nobody 21966 0.0 0.4 23716 2192 ? S Mar16 0:16 nginx: worker process root 29249 0.0 0.1 11744 920 pts/0 S+ 23:15 0:00 grep --color=auto nginx ``` 结果是出来了,但你有没有注意到最后多出了一行?那是什么?那其实就是我们的 **grep 命令本身的命令行**。 因为它的命令行中也包含了被筛选的内容(nginx),所以自然也会被包含进来。 所以怎么去掉?先说答案,像下面这样即可: ```bash $ ps aux | grep '[n]ginx' root 9926 0.0 0.1 23388 948 ? Ss Mar15 0:00 nginx: master process ./sbin/nginx nobody 21964 0.0 0.3 23388 1536 ? S Mar16 0:16 nginx: worker process nobody 21965 0.0 0.4 23388 2032 ? S Mar16 0:14 nginx: worker process nobody 21966 0.0 0.4 23716 2192 ? S Mar16 0:16 nginx: worker process ``` ## 解释 [`grep`](https://en.wikipedia.org/wiki/Grep)(全局正则表达式打印,Global Regular Expression Print),是一种**正则表达式模式匹配工具**:在输入中搜索/匹配内容,并打印出匹配的内容。 也就是说在搜索匹配时执行的是**模式匹配**,而**不是简单地单个字符的依次比较匹配**。 在上面的用例中,`[n]`这个模式在正则表达式中的含义是:**中括号中的字符`n`必须出现一次**。那么`grep '[n]ginx'`的含义是:**在输入中搜索匹配`nginx`这个字符串**。 因此就含义本身而言,**` grep '[n]ginx'`整体的含义等价于`grep nginx`**。也即:**如果输入相同,它们匹配到的内容完全相同。** 那么,前面的例子怎么解释?很好解释:**两个命令的输入并不相同**。尽管它们的输入都是来自`ps aux`,但是`grep`这个**命令本身的命令行**在被`ps`枚举出来时是不一样的。 - 对于`ps aux | grep nginx`,grep 命令的命令行是:`grep nginx`; - 对于`ps aux | grep '[n]ginx'`,grep 命令的命令行是:`grep [n]ginx`; 由于`grep nginx`和`grep '[n]ginx'`都是在输入中搜索匹配`nginx`,但是因为上述后者的`grep`命令行为`grep [n]ginx`。因此**不被包含在结果中**。这,就是原因。 ## 为什么要加单引号? **像上面这样的比较简单的字符串的匹配,不加引号基本上不会出错。** 但是,在 bash 这种**完全基于字符串**的语言中,**任何人都能轻易写出漏洞百出的脚本代码**。 比如就本文的例子而言:`[]`不仅是正则表达式中的特殊字符,它还是**bash的路径名展开(pathname expansion)**语法的一部分:`bash`这个shell在执行我们的命令前会**分析我们输入的命令**, 并且执行若干个展开步骤中的**路径名展开(pathname expansion)**,最后才是执行命令。 那么何为路径名展开?你可以简单地理解为**替换通配符**,就像我们可以用`*`代替当前目录下的所有文件一样。 展开有成功也有失败: - 若成功:展开前的特殊字符被路径名替换; - 若失败:默认保持不变; 举个例子: - 如果当前目录下**没有**一个名为`nginx`的文件,`grep [n]ginx`(注意没有引号)会展开成`grep [n]ginx`,没变。 - 如果当前目录下**有**一个名为`nginx`的文件,`grep [n]ginx`(注意没有引号)会展开成`grep nginx`,中括号没了。 所以,后者得不到期望的结果。 为什么加单引号?**单引号可以避免 bash 做路径名展开**。 单引号会作为参数的一部分传递给`grep`吗?**不会**,bash 还会执行一个叫`Quote Removal`的步骤,将(不是用户特意产出的)引号给移除掉。 参考链接: - [Quick Shell Tip: Remove grep command while grepping something using ps command](http://www.cyberciti.biz/tips/grepping-ps-output-without-getting-grep.html) - [Bash: 3.5.8 Filename Expansion](http://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#index-pathname-expansion) - [Bash: 3.5.9 Quote Removal](http://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Quote-Removal)