Grep:星号(*)并不总是有效

如果我grep包含以下内容的文档:

ThisExampleString

…对于表达式This*String*String ,不返回任何内容。 但是, This*按预期返回上面的行。

表达式是否用引号括起来没有区别。

我以为星号表示任意数量的未知字符? 为什么它只在表达式的开头才起作用? 如果这是预期的行为,我使用什么而不是表达式This*String*String

正则表达式中的星号表示“匹配前面的元素0次或更多次”。

在您使用grep 'This*String' file.txt的特定情况下,您试图说,“嘿,grep,匹配我的单词Thi ,然后是小写s零次或多次,后跟单词String ”。 在Example找不到小写的s ,因此grep忽略ThisExampleString

grep '*String' file.txt的情况下,你说的是“grep,匹配我的空字符串 – 几乎没有 – 在单词String ”。 当然,这不是应该如何读取ThisExampleString 。 (还有其他可能的含义 -你可以尝试使用和不使用-E标志 – 但没有任何意义与你真正想要的一样。)

知道了. 表示“任何单个字符”,我们可以这样做: grep 'This.*String' file.txt 。 现在grep命令将正确读取它:后跟任何字符(将其视为ASCII字符的选择)重复任意次,然后是String

BRE 1 s,ERE 1 s和PCRE 1 s中的*元字符匹配先前分组模式的0或更多次出现(如果分组模式在*元字符之前),前一个字符类出现0或更多(如果是字符) class在* metacharacter之前)或0或更多前一个字符的出现(如果分组模式和字符类都不在*元字符之前);

这意味着在This*String模式中, *元字符前面没有分组模式或字符类, *元字符匹配前一个字符的0个或更多个出现(在本例中为s字符):

 % cat infile ThisExampleString ThisString ThissString % grep 'This*String' infile ThisString ThissString 

要匹配任何字符的0或更多出现,您希望匹配0或更多的出现. 元字符,匹配任何字符:

 % cat infile ThisExampleString % grep 'This.*String' infile ThisExampleString 

BRE和ERE中的*元字符始终是“贪婪的”,即它将匹配最长的匹配:

 % cat infile ThisExampleStringIsAString % grep -o 'This.*String' infile ThisExampleStringIsAString 

这可能不是理想的行为; 如果不是,你可以打开grep的PCRE引擎(使用-P选项)并附加? 元字符,当放在*+元字符之后具有改变其贪婪的效果:

 % cat infile ThisExampleStringIsAString % grep -Po 'This.*?String' infile ThisExampleString 

1:基本正则表达式,扩展正则表达式和Perl兼容正则表达式

其中一个解释链接 :

星号“ * ”在正则表达式中与通配符中的含义不同; 它是一个修饰符,适用于前面的单个字符或表达式,如[0-9]。 星号匹配前面的零个或多个。 因此, [AZ]*匹配任意数量的大写字母,包括无,而[AZ][AZ]*匹配一个或多个大写字母。

*具有特殊含义,既可以作为shell通配符(“通配符”),也可以作为正则表达式元字符 。 你必须考虑到这两点,但是如果你引用正则表达式,那么你可以防止shell专门处理它并确保它不加改变地传递给grep 。 虽然概念上有点类似,但是对于shell来说,它意味着与grep含义完全不同。

首先 ,shell将*视为通配符。

你说:

表达式是否用引号括起来没有区别。

这取决于运行命令时您遇到的目录中存在哪些文件。 对于包含目录分隔符/ ,它可能取决于整个系统中存在的文件。 你应该总是引用 grep正则表达式 – 单引号通常是最好的 – 除非你确定你对执行grep命令之前 shell执行的九种类型的可能令人惊讶的转换是可以接受的。

当shell遇到未引用的*字符时,它将其表示为“零或更多任何字符”,并将包含它的单词替换为与该模式匹配的文件名列表。 (以.开头的文件名被排除在外 – 除非您的模式本身以. 或者您已将shell配置为包含它们。)这称为通配 – 以及名称文件名扩展路径名扩展

使用grep的效果通常是将第一个匹配的文件名作为正则表达式 – 即使对于人类读者而言,它不是一个正则表达式 – 而所有其他文件名都是自动列出的你的glob被视为搜索匹配项的文件。 (你没有看到列表 – 它被不透明地传递给grep 。)你几乎从不希望这种情况发生。

有时候不是问题的原因 – 在你的特定情况下,至少到目前为止 ,它不是 – 如果满足以下所有条件*将保持不变:

  1. 没有名称匹配的文件。 …或者您已经在shell中禁用了globbing,通常使用set -f或等效的set -o noglob 。 但这种情况并不常见,你可能知道你做到了。

  2. 您正在使用一个shell,其默认行为是在没有匹配的文件名时保留* 。 在Bash中就是这种情况,您可能正在使用它,但在所有Bourne样式的shell中都没有。 (例如,常用shell Zsh中的默认行为是globs要么(a)展开,要么(b)产生错误。) 或者你已经改变了shell的这种行为 – 如何做到各不相同跨壳。

  3. 你还没有告诉你的shell在没有匹配的文件的情况下允许globs替换为什么,在这种情况下也没有失败并显示错误信息。 在Bash中,分别通过启用nullglobfailglob shell选项来完成。

你有时可以依靠#2和#3,但你很少依赖#1。 当您有不同的文件或从其他位置运行时,带有不带引号的模式的grep命令可能会停止工作。 引用你的正则表达式,问题就消失了。

然后 grep命令将*视为量词。

其他答案 – 例如Sergiy Kolodyazhnyy和kos的答案 – 也以不同的方式解决了这个问题的这个方面。 因此,我鼓励那些尚未阅读过的人在阅读本答复的其余部分之前或之后这样做。

假设*确实使它成为grep – 引用应该确保 – grep然后将其视为表示它之前的项可能发生任意次 ,而不是必须恰好发生一次 。 它可能仍然会发生一次。 或者根本不存在。 或者它可以重复。 适合任何这些可能性的文本将匹配。

“item”是什么意思?

  • 单个字符 。 由于b匹配文字bb*匹配零个或多个b s,因此ab*c匹配acabcabbcabbbc等。

    同样,因为. 匹配任何字符 , .*匹配零个或多个字符1 ,因此a.*c匹配acakcahjglhdfjkdlgjdfkshlgc ,甚至acccccchjckhcc等。 或者

  • 一个角色类 。 由于[xy]匹配xy[xy]*匹配零个或多个字符,其中每个字符都是xy ,因此p[xy]*q匹配pqpxqpyqpxxqpxyqpyxqpyyqpxxxqpxxyq

    这也适用于\w\W\s\S等速记forms的字符类。 由于\w匹配任何单词字符, \w*匹配零个或多个单词字符。 要么

  • 一组 。 由于\(bar\)匹配bar\(bar\)*匹配零个或多个bar s,因此foo\(bar\)*baz匹配foobazfoobazfoobazfoobaz等。

    使用-E-P选项, grep将您的正则表达式分别视为ERE或PCRE ,而不是BRE ,然后组被( )而不是\( \)包围,那么您将使用(bar)而不是\(bar\)foo(bar)baz而不是foo\(bar\)baz

man grep给出了一个合理可访问的BRE和ERE语法解释,并列出了grep在开头接受的所有命令行选项。 我建议将手册页作为资源,以及GNU Grep文档和本教程/参考站点 (我已链接到上面的多个页面)。

为了测试和学习grep ,我建议用模式调用它,但没有文件名。 然后它从您的终端接收输入。 输入行; 回显给您的行是包含您的模式匹配的文本的行。 要退出,请在行的开头按Ctrl + D ,这表示输入结束。 (或者您可以像大多数命令行程序一样按Ctrl + C. )例如:

 grep 'This.*String' 

如果使用--color标志, grep将突出显示与正则表达式匹配的行的特定部分 ,这对于确定正则表达式的作用以及查找正在查找的内容非常有用。 默认情况下,当您从命令行运行grep时,Ubuntu用户有一个导致grep --color=auto运行的Bash别名 – 这足以达到此目的 – 所以您甚至可能不需要传递--color手动--color

1 因此.*在正则表达式中表示*表示在shell中。 但是,区别在于grep自动在其中的任何位置打印包含匹配的行,因此通常不需要在正则表达式的开头或结尾处使用.*