grep是如何工作的?

17

我试图理解 grep 的工作原理。

当我说 grep "hello" *.* 时,grep 得到了两个参数 — (1) 要搜索的字符串,即 "hello" 和 (2) 路径 *.*?还是 shell 将 *.* 转换为 grep 可以理解的内容?

我可以在哪里获取 grep 的源代码?我看到了这个GNU grep 的链接。其中一个 README 文件说它与 unix grep 不同。如何不同?

我想查看 FreeBSD 版本和 Linux 版本的 grep 源代码(如果它们不同)。


1
这里提供了grep的UNIX man页面作为参考:http://compute.cnr.berkeley.edu/cgi-bin/man-cgi?grep。这是FreeBSD版本:http://www.freebsd.org/cgi/man.cgi?query=grep,这是Linux版本:http://linux.die.net/man/1/grep。 - Devin M
1
一个浏览旧的UNIX源代码的好地方是http://www.tuhs.org。 - luser droog
@luser droog:感谢您提供的链接,太棒了 :) - hari
1
@hari:如果你足够勇敢,可以看一下版本3中的C编译器;它真的很可怕。指针声明为a[]而不是*a - luser droog
@hari:我记错了,它是来自2号版本。http://minnie.tuhs.org/cgi-bin/utree.pl?file=V2/c/ncc.c - luser droog
显示剩余2条评论
4个回答

22

grep 的威力源于自动机理论的魔法。GREP 是 Global Regular Expression Print 的缩写。它通过构建一个自动机(一个非常简单的“虚拟机器”:不是图灵完备的),并针对输入流“执行”该自动机来工作。

自动机是一个节点或状态的图形或网络。状态之间的转换取决于正在检查的输入字符。特殊的自动机,如 +*,通过具有回到自身的转换来工作。字符类别如 [a-z] 由扇形表示:一个起始节点,带有每个字符的分支朝向“辐条”;通常,“辐条”具有特殊的“epsilon 转换”,可以连接到下一个从正则表达式(搜索字符串)构建的自动机,使其能够链接起来。ε 转换允许在不向前移动正在搜索的字符串的情况下更改状态。

编辑:看起来我没有仔细阅读问题。

当您键入命令行时,它首先由 shell 进行预处理。shell 执行别名替换和文件名扩展。在替换别名后(它们像宏一样),shell 将命令行分成参数列表(以空格分隔)。这个参数列表作为整数计数(通常称为 argc)和指向以 NULL 结尾的 char 数组的指针传递给可执行命令程序的 main() 函数。

各个命令可以随意使用它们的参数。但是,大多数 Unix 程序会在给出 -h 参数时打印友好的帮助消息(因为它以减号开头,所以被称为选项)。GNU 软件还将接受“长格式”选项 --help

由于不同版本的 Unix 程序之间存在很大的差异,发现程序所需确切语法最可靠的方法是询问该程序本身。如果这不能告诉你需要什么(或者太难以理解),下一步应该检查本地 man 页面(man grep)。对于 GNU 软件,您通常可以从 info grep 中获取更多信息。


2
仅做一点小挑剔:GREP 不是 General Regular Expression Parser 的缩写。它是 vi/ex 模式命令 :g/re/p 的缩写,代表 global/regular expression/print - Jens
2
@Jens:grepvi/ex 更早一些;它模拟的是 ed 命令 g/re/p,在 vi 模式下被翻译成了 :g/re/p(在 ex 中也是 g/re/p)。但总体来说是正确的。 - Jonathan Leffler

13

Shell会处理通配符(将*形式转换为文件名)。如果你有一个简单的C程序,你就可以看到这一点:

#include <stdio.h>

int main(int argc, char **argv) {
    for(int i=1; i<argc; i++) {
        printf("%s\n", argv[i]);
    }
    return 0;
}

然后像这样运行:

./print_args *

您会看到它打印出了匹配的内容,而不是字面上的 *。如果您像这样调用它:

./print_args '*'

你会看到它得到了一个字面上的 *


谢谢你的回答。在grep命令中,第二个参数是什么?文件列表?还是一次只能处理一个文件? - hari
@hari:把grep想象成一个简单的C程序。如果你给grep一个*,它会得到一堆文件名作为额外的参数。但是,如果你像这样引用*'*',那么shell就不会进行globbing,grep只会得到单个的*参数(除非你的当前目录中恰好有一个名为*的文件,否则这可能会失败)。 - icktoofay
哇,那么如果该目录有1000个文件,“*”会将其作为1000个参数传递吗?(每个参数都是单个文件)? - hari
@hari:是的,试试看。如果你有一堆需要这样操作的文件,最好使用findxargs - icktoofay
4
从技术上讲,“echo *”已经足够展示通配符了。 - GreyCat

6

shell会将'*.*'扩展为文件名列表,并将扩展后的文件名列表传递给像grep这样的程序。而grep程序本身不会扩展文件名。

因此,回答您的问题:grep没有得到2个参数;shell将'*.*'转换为grep可以理解的内容。

GNU grep与Unix grep不同,支持额外的选项,例如-w-B-A

在我看来,FreeBSD使用GNU版本的grep


3
如何使用通配符参数取决于您的 shell。标准 Bourne shell 有一个开关 (-f) 可以禁用文件名 globbing (请参阅 man 手册)。
您可以在脚本中激活此开关。
set -f

1
是的,但这很少使用。所有Unix/Linux shell默认都会进行通配符扩展。如果你禁用了globbing并键入grep“hello”*.*grep将看到*.*并将其视为文件名(除非你碰巧有一个该名称的文件),否则可能会失败。 - Keith Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接