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


11

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

ThisExampleString

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

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

我以为星号表示有多少个未知字符?为什么仅在表达式的开头才起作用?如果这是预期的行为,那么我将使用什么代替表达式This*String*String


因为那不是正则表达式的工作原理……(尤其是:* != any number of unknown characters。阅读文档。)
njzk2

Answers:


18

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

在您使用的特殊情况下grep 'This*String' file.txt,您尝试说:“嘿,grep,请给我匹配单词Thi,然后小写s零次或多次,然后再单词String”。小写字母s无处可寻Example,因此grep会忽略ThisExampleString

在的情况下grep '*String' file.txt,您说的是“ grep,请给我匹配一个空字符串-实际上什么都没有-在单词之前String”。当然,这不是ThisExampleString应该阅读的方式。(还有其他可能的含义 -您可以在有和没有-E标志的情况下尝试使用-但其中的任何含义都不是您真正想要的含义。)

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


6
在bash(和大多数Unix外壳的)*是一个特殊字符,它应该被引用或转义比如像这样:grep 'This*String' file.txt或者这样:grep This\*String file.txt不被意外的结果感到惊讶。
pabouk

2
@pabouk在外壳中,*是通配符。在grep中,*是正则表达式运算符。参见unix.stackexchange.com/q/57957/70524
muru

11
pabouk是正确的,文件名扩展是在运行命令之前进行的;比较strace grep .* file.txt |& head -n 1 strace grep '.*' file.txt |& head -n 1。实际上grep也可以与任何Unicode字符一起使用(例如echo -ne ⇏ | grep ⇏输出
kos 2015年

1
@Serg:您在这里享有很高的声誉,所以我认为您会立即注意到我的意思。OP已将问题标记为bash,因此我假设所讨论的命令由解释bash。这意味着首先bash解释其特殊字符,并且仅在执行完所有扩展之后,才将参数传递给生成的进程。-----例如,在Bash:中的此命令grep This.\*String file.txt/bin/grep使用以下参数0:grep,1 : This.*String、 2:生成file.txt。请注意,Bash删除了反斜杠,并且原先转义的字符已按原义*传递。
pabouk 2015年

7
有趣的是(并且为了排除故障非常麻烦:)您的命令grep This.*String file.txt通常可以正常工作,因为很可能不会有与外壳通配符表达式匹配的文件This.*String。在这种情况下,默认情况下,Bash将按原样传递包含的参数*
pabouk 2015年

8

*在BRE元字符1个 S,ERE 1秒和PCRE 1 S匹配0或多个正好先前分组图案的(如果是分组图案前述*元字符),前一个字符类的0或多个正好(如果字符类是*(如果在该元字符之前没有分组模式或一个字符类),或者前一个字符出现0次或更多次*

这意味着在This*String模式中,作为*元字符而不是分组模式或字符类之前的*元字符,该元字符匹配前一个字符(在此情况下为s字符)出现0次或更多次:

% 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

这可能不是理想的行为;如果不是,则可以打开grepPCRE引擎(使用该-P选项)并附加?元字符,将其放在*+元字符之后可改变其贪婪程度:

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

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


感谢您提供的非常有帮助的答案。但是,我选择了不同的答案,因为它更短并且更容易理解。+1提供了这么多的细节。
Trae 2015年

@Trae不客气。很好,我同意这可能太复杂了,并且对不太熟悉该主题的人做出了太多假设。
kos 2015年

4

在这里找到一种解释链接

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


1

*作为shell globbing字符(“通配符”)和正则表达式metacharacter都有特殊含义。您必须将两者都考虑在内,尽管如果您引用正则表达式,则可以防止shell对其进行特殊对待,并确保将shell不变地传递给grep。尽管概念上来说有点相似,但是*对shell的含义与对shell的含义却大不相同grep

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

你说:

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

这取决于运行命令时碰巧所在目录中的文件。对于包含目录分隔符的模式/,它可能取决于整个系统中存在哪些文件。你应该总是引用正则表达式grep--and 单引号通常best-- ,除非你确定你是好与九种可能令人惊讶的转变的外壳否则执行之前执行的grep命令。

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

的效果grep通常是将第一个匹配的文件名当作正则表达式使用-即使对于人类读者来说很明显这并不意味着是正则表达式-而所有其他文件名会从glob被用作其中搜索匹配项的文件。(您看不到该列表,它是不透明地传递给的grep。)您几乎从不希望这种情况发生。

究其原因,这是有时不是一个问题-在您的特定情况下,至少到目前为止,它wasn't -是,*将被单独留在家中,如果以下所有条件都为真

  1. 没有文件的名字相匹配。 ...或者您通常使用set -f或等效项禁用了外壳中的Globing set -o noglob。但这并不常见,您可能会知道自己做到了。

  2. 您使用的外壳程序的默认行为是*在没有匹配的文件名时不理会。在您可能正在使用的Bash中就是这种情况,但并非在所有Bourne风格的shell中都是如此。(例如,流行的Shell Zsh中的默认行为是使glob (a)扩展或(b)产生错误。)...或者您已经更改了Shell的这种行为-这样做的方式各不相同跨壳。

  3. 您还没有以其他方式告诉你的shell,让水珠与被替换什么时,有没有匹配的文件,也没有失败,在这种情况下的错误消息。在Bash中,可以分别通过启用nullglobfailglob shell选项来完成。

有时您可以依靠#2和#3,但很少可以依靠#1。一个grep与现在的作品可能会停止,当你有不同的文件,或者当您从不同的地方运行工作的不带引号模式命令。引用您的正则表达式,问题就消失了。

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

其他答案(例如Sergiy Kolodyazhnyykos的答案)不同的方式解决了这个问题的这一方面。因此,我鼓励在阅读本答案其余部分之前或之后尚未阅读的人进行阅读。

假设*确实做到了grep(应确保引用),grep然后将其视为意味着它前面的项目可以出现任意次,而不必完全发生一次。它仍然可能发生一次。或者它可能根本不存在。或者可以重复。符合所有这些可能性的文本将被匹配。

我所说的“项目”是什么意思?

  • 一个字符。由于b匹配文字bb*匹配零个或多个bS,从而ab*c匹配acabcabbcabbbc,等。

    类似地,由于.匹配的任何字符.*匹配零个或多个字符1,从而a.*c匹配acakcahjglhdfjkdlgjdfkshlgc,即使是acccccchjckhcc或者

  • 一个字符类。由于[xy]火柴xy[xy]*匹配零个或多个字符,其中每一个或者是xy,从而p[xy]*q匹配pqpxqpyqpxxqpxyqpyxqpyyqpxxxqpxxyq,等。

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

  • 一个小组。由于\(bar\)火柴bar\(bar\)*匹配零个或更多barS,从而foo\(bar\)*baz匹配foobazfoobarbazfoobarbarbazfoobarbarbarbaz,等。

    使用-E-P选项,grep将您的正则表达式分别视为EREPCRE,而不是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 --color=auto当您从命令行运行时,Ubuntu用户具有一个Bash别名,可以grep使它运行(对于此目的就足够了),因此您甚至不需要--color手动传递。

1 因此.*,在正则表达式中是*指Shell glob中的含义。但是,不同之处在于,它会grep自动在其中任何位置打印包含您的匹配项的行,因此通常不需要.*在正则表达式的开头或结尾处都包含该行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.