grep 不会对具有特定扩展名的文件进行递归

grep 不会对具有特定扩展名的文件进行递归

每当我使用 grepgnuwin32的递归选项-r并包含要搜索的文件的 glob 模式(例如*.c),则不会搜索子目录中的任何文件。我正在使用 gnuwin32 的最新 grep。

具体来说,我在目录中的所有 c 源文件中搜索字符串“iflag”。

grep -r iflag *.c

答案1

Grep 的选项(与、、和选项-r相同)-R--recursive-d recurse--directories=recurse以目录名称(或模式)作为参数。您尝试执行的命令应解释为“从当前工作目录开始递归匹配模式 *.c 的所有目录。在每个目录中搜索所有文件中的字符串 iflag。”

OP 可能想要的 grep 命令是

grep --include \*.c -r iflag .

其解释为“从当前工作目录开始递归搜索所有子目录。在每个子目录中搜索与模式 *.c 匹配的所有文件中的字符串 iflag。”

答案2

我不确定为什么递归标志不起作用,但这里有一个对我有用的解决方法。-r选项接受一个参数:要搜索的目录。要搜索当前目录,请为其提供参数.。例如

grep regexp-to-find -r . --include=*.c

编辑

这实际上是 grep 的预期行为,与在 Windows 上运行它无关。该-r选项采用目录参数。查看 HairOfTheDog 的答案以了解原因。

答案3

我发现目前给出的答案太复杂了。只需使用:

grep -r --include="*.c" searchString .

(根据提议基督教徒堆栈溢出狗的毛 在上面的评论中。)

如果你懒得一直输入这些代码,那么只需定义一个函数并将其添加到“~/.bashrc”即可。(由于使用了参数,因此无法使用普通别名,如上文所述堆栈溢出

rgrep() {
  grep -r --include="$2" "$1" .
}

现在您有一个易于使用的递归 grep。例如,如果您想在所有文本文件中搜索“字符串”,请使用:

rgrep string "*.txt"

答案4

解决方案

对于您想要做的事情,使用--include是正确的方法;功劳归于其他答案。


这个答案背后的理由

到目前为止还没有发布任何解释为什么您的原始代码没有像您预期的那样工作。这就是我的答案试图填补的空白。


为什么你的代码没有按预期工作

您的原始代码是:

grep -r iflag *.c

并且您希望grep递归地(因为-r)检查当前工作目录并仅考虑与模式匹配的文件*.c

要了解发生了什么,最好先分析一下这个命令在 Linux 中的行为。您提到了 GnuWin,您的操作系统是 Windows,您可能grep在里面使用过cmd.exe;这带来了一些有趣的细微差别(我们将讨论它们),但您的设计仍然是为了在 Unix/Linux 中grep模仿 GNU 。grep

假设您在 Linux 中运行该命令一段时间。在 Linux 中,它的工作方式类似,即它也不会给您预期的结果。

Linux 中发生的第一件重要的事情是 shell 尝试扩展*.c 它以grep.开头*.c,可能扩展为多个参数:当前工作目录中匹配文件的名称。例如,如果有两个匹配的文件foo.cbar.c那么您的命令实际上将是:

grep -r iflag bar.c foo.c

两个重要事实:

  1. 以上内容grep并未意识到*.c被使用。它将-riflagbar.cfoo.c作为其参数。
  2. 如果foo.cbar.c是常规文件,则-r无关紧要。另一方面,如果例如foo.c文件目录类型则将-r进入grep该目录;但grep将读取目录中的所有文件(它仍然不知道*.c您输入的内容)。

在某些 shell 中,您可以使用可以匹配子目录中文件的 glob。在您的例子中,它将类似于**/*.c。此功能称为“globstar”,可能需要在 shell 中明确启用它。如果您正确使用了这种模式,shell 会将所有匹配的路径传递给grep(例如dir1/subdir/baz.c),grep而不会看到模式本身,-r可能无关紧要。选择与模式匹配的所有路径名的工作将由 shell 完成。但是您的模式不是这样的。

有两种情况grep实际上会出现这种情况*.c

  1. 如果当前工作目录中没有匹配项,则(在一些贝壳里)*.c将不会被扩展,并且它将被grep按字面意义传递给*.c
  2. 如果您对中的星号进行转义或引用*.c(例如:\*.c,, ) "*.c"'*'.c那么它将不会被扩展并且该单词将被grep逐字传递为*.c

这无法帮助您,因为grep它本身并非设计用于扩展其解释为路径名的参数中的模式。如果它看到*.c它期望路径名的位置,那么它将尝试读取名为的文件*.c。有些工具实际上在某些情况下会解释此类模式(实际上grep --include是一个例子)。

即使grep设计为*.c以您期望的方式将其解释为代码中的模式,在许多情况下您的代码也无法工作。您需要引用或转义该模式以保护它免受 shell 的攻击(请参阅这个问题)。

简而言之,您的代码无法运行的原因是*.c先展开(grep运行前),然后递归才开始(grep运行时)。您需要反过来做,一种方法是使用--include=*.c(另一种方法是使用 globstar,其中 shell 处理递归,但它可能会给您带来argument list too long错误;所以最好让grep这样做)。

注意--include=*.c仍然是 shell 可能扩展的模式;如果您希望代码健壮,则应该将星号转义或加引号。最好知道命令(一般来说:任何命令)的哪些部分(或可能)由 shell 扩展。


Windows 的细微差别

现在,最后,谈谈 Windows 的细微差别。Windowsgrep中的命令来自 GnuWin 项目,其设计旨在合理地模仿grepLinux 中的 GNU。我们看到,在 Linux 中,命令*.c首先被展开,然后开始递归。在 Unix/Linux 中,我们grep对此无能为力,因为工具*.c在 shell 展开之后启动。在 Windows 中则有所不同。

如果grepgrep.exe)实际上是 Windows 中的可执行文件,并且您以cmd.exe如下方式运行它

grep -r iflag *.c

(就像你可能做的那样)然后可执行文件将要*.c在展开之前能够看到。在 Unix/Linux 中grep(一般任何可执行文件)在 shell “消化” 以字符串形式呈现的命令后获取参数数组。在 Windows 中grep.exe(一般任何可执行文件)获取细绳(有点“消化”的字符串,仍然是字符串),而不是数组;它为自己创建一个数组。在你的情况下,字符串包含*.c并且扩展是在grep开始之后完成的grep,而不是在之前完成的cmd.exe。像 Windows 中的许多工具一样grep,可能不费心提供自定义函数来从字符串获取参数数组,而是使用 Windows 提供的一些标准函数,因此它看起来就像cmd.exe做的工作一样。甚至Windows 中grep.exe假装的创建者也*.c通过 shell 进行了扩展;这里他们说“文件名通配符由命令 shell 解释,而不是由程序解释”。这并不完全正确。请参阅我的这个答案了解更多详细信息。

这一切意味着如果的创作者grep.exe想要你的精确的命令(带有未加引号和未转义的星号)的行为符合您的预期(首先是递归,然后是模式匹配),那么在 Windows 中它们可以做到这一点;在 Linux 中它们不能,不可靠,除非您保护星号不被扩展,否则您的 shell 会干扰。请注意,未加引号/未受保护的星号*.c不被 shell 扩展是一种非标准行为,即使在 Windows 中也是如此(我们应该说“*.c不像被 shell 扩展”)。所以即使每个人想要你的精确的命令(使用未引用未转义grep如果你不想让你的 .asterisk 程序运行得和你预期的完全一样(永远不受 shell 的干扰),那么它无论如何都不会在 GNU或 GnuWin中实现,grep.exe因为它在 Unix/Linux 中是不可能的,而且也不符合 Unix/Linux 和 Windows 中既定的标准。

但这并不重要,因为当前的行为grep(我的意思是将文件名解释为不作为模式,即使它们看起来像模式,即使-r被使用)很好,并且没有必要改变它。

还要注意,Windows 中的引用规则与 Unix/Linux 中的 shell 不同。grep.exe当它获取一个字符串时,在将字符串转换为参数数组之前,可以看到使用了引号。通过提供一个自定义函数将字符串转换为数组,grep.exe可以至少在某种程度上施加不同的引用规则,可能更像 Unix 规则。它没有强加这样的规则这一事实是使用标准函数的另一个线索。在我看来,grep.exeWindows 中的 shell 以最像 Windows 的方式执行 Linux 中的 shell 所负责的工作(即使在 Windows 中它可以将某些功能更改为像 Linux 一样);并且它以最grep像 GNU 的方式执行 Linux 中的 GNUgrep所负责的工作。

相关内容