需要一个用于多行搜索的正则表达式(grep)。

288

我正在运行一个 grep 命令,以查找任何一个包含单词 select,后跟单词 customerName,再后面是单词 from 的 *.sql 文件。该 select 语句可以跨越多行,并且可能包含制表符和换行符。

我已经尝试了以下几种变化:

$ grep -liIr --include="*.sql" --exclude-dir="\.svn*" --regexp="select[a-zA-Z0-
9+\n\r]*customerName[a-zA-Z0-9+\n\r]*from"

然而,这只会无限运行。有人能帮我使用正确的语法吗?


6
您在此指定的grep命令会因为您没有在命令结尾指定要搜索的文件而一直运行。'--include'是一个用于筛选文件名的过滤器,实际上并不提供任何需要被筛选的文件。 - marklark
3个回答

632
无需安装grep变体pcregrep,您可以使用grep进行多行搜索。
$ grep -Pzo "(?s)^(\s*)\N*main.*?{.*?^\1}" *.c

解释:
-P 激活grep的perl-regexp功能(正则表达式的强大扩展)
-z 将输入视为一组以零字节(ASCII NUL字符)结尾的行,而不是换行符。也就是说,grep知道行的结束位置,但将输入视为一个大行。请注意,如果与-o一起使用,这也会添加一个尾随的NUL字符,请参阅注释。
-o 仅打印匹配项。由于我们使用了-z,整个文件就像一个大行,所以如果有匹配项,整个文件都会被打印出来;这样就不会发生这种情况。
在正则表达式中:
(?s) 激活PCRE_DOTALL,这意味着.可以匹配任何字符或换行符
\N 查找除换行符外的任何内容,即使已激活PCRE_DOTALL
.*? 以非贪婪模式查找.,也就是尽快停止。

^ 寻找行的开头

\1 回溯到第一组(\s*)。这是尝试找到方法相同的缩进。

正如你所想象的,这个搜索会在 C(*.c)源文件中打印出主方法。


16
/bin/grep: 无法同时使用 -P 和 -z 选项。 - Oli
9
/bin/grep: PCRE 不支持 \L、\l、\N、\U 或者 \u。 - Oli
33
"对于我的多行需求,-zo已经足够了,谢谢!(已点赞。)" - Szocske
22
建议使用“grep -Pazo”替代不安全的“-Pzo”。解释:在非ASCII文件上使用-z开关可能会触发grep的“二进制数据”行为,从而改变返回值。开关“-a | --text”可以防止这种情况发生。 - rloth
12
如果你需要将匹配结果分行显示,请使用管道符号|将结果传输给 tr '\0' '\n' 命令!请注意,这不会改变匹配结果,只是将它们按独立的行显示。 - t.animal
显示剩余24条评论

214
我在grep方面不是很擅长。但是你的问题可以使用AWK命令来解决。你可以试试看。
awk '/select/,/from/' *.sql

以上代码将从第一个出现的 select 到第一个出现的 from 的结果。现在您需要验证返回的语句是否具有 customername。为此,您可以使用管道操作结果。然后可以再次使用 awk 或 grep。

7
超棒的简单解决方案。注意:在 AWK 的“区间模式”中,逗号用作分隔符。详细解释请参见 AWK 用户指南的第 7.1.3 节“使用模式指定记录区间”的完整说明。 - Olivier
5
为了完整性:这也适用于(更简单的)sed:sed -n '/select/,/from/p' whatever.sql - Joshua S

8
你的根本问题在于 grep 一次只能处理一行文本,所以无法找到跨越多行的 SELECT 语句。
你的第二个问题是你使用的正则表达式不能处理 SELECT 和 FROM 之间可能出现的复杂情况 - 特别是它省略了逗号、句号和空格,但也包括引号和任何可能出现在引号内部的内容。
我建议采用基于 Perl 的解决方案,让 Perl 每次读取一整段并对其应用正则表达式。缺点是需要处理递归搜索 - 当然有模块可以做到这一点,包括核心模块File::Find
概括地说,对于单个文件:
$/ = "\n\n";    # Paragraphs

while (<>)
{
     if ($_ =~ m/SELECT.*customerName.*FROM/mi)
     {
         printf file name
         go to next file
     }
}

需要将其封装到一个子程序中,然后由File::Find的方法调用。

2
Grep 不是逐行工作的。它会在整个语料库中搜索匹配项,只有当它找到匹配项时才会回头考虑是否存在换行符。这样,它就不必扫描整个语料库以寻找新行(这会显著减慢速度)。 - Squidly
@MrBones:现代实现的grep有可能使用mmap()将文件读入内存,但它的操作模式是由POSIX规范grep定义的,并且它明确地按行工作。不过我还不确定;如果文件大小超过几个GB,那么当你可以一次只读取几KB时,就没有必要将其全部映射到内存中(大多数带有行的文件的行长度都小于1KB)。当然,还有JSON文件,但它们是例外。 - Jonathan Leffler
2
它在行的层面上是有效的,但一次只能处理一行。没有循环执行类似于(for line in lines: doesMatch(line))的操作。当考虑到fgrep(固定字符串)和Boyer-Moore算法的工作原理时,这更为明显。mmap并不是真正相关的。 - Squidly
这还增加了很多问题,除了原来的问题,还有正则表达式的问题! - user3791372
1
@Squidly 无论这是否正确,都不能改变它逐行处理的事实。编程方式与其工作方式并不相等,是吗? - Pryftan
1
Grep 一次只能处理一行。如果使用 -z 选项,则它的“一行”将被解释为整个文件,因为其中没有 NUL 字符来分隔。 - rogerdpack

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