【问题标题】:Problem with using grep to match the whole word使用 grep 匹配整个单词的问题
【发布时间】:2019-03-26 22:36:23
【问题描述】:

我正在尝试匹配新行分隔字符串列表中的整个字符串。这是我的例子:

[hemanth.a@gateway ~]$ echo $snapshottableDirs
/user/hemanth.a/dummy1 /user/hemanth.a/dummy3
[hemanth.a@gateway ~]$ echo $snapshottableDirs | tr -s ' ' '\n'
/user/hemanth.a/dummy1
/user/hemanth.a/dummy3
[hemanth.a@gateway ~]$ echo $snapshottableDirs | tr -s ' ' '\n' | grep -w '/user/hemanth.a'
/user/hemanth.a/dummy1
/user/hemanth.a/dummy3

我的目标是仅当且仅当字符串 /user/hemanth.a 在字符串列表中作为整个单词(在新行中)存在时才找到匹配项。但是上面的命令也返回了包含/user/hemanth.a的字符串。

这是一个示例场景。无法保证我想要匹配的所有字符串都采用/user/xxxxxx.x 的形式。理想情况下,如果它作为列表中的整个单词存在于新行中,我希望匹配确切的字符串。

任何帮助将不胜感激。谢谢。

【问题讨论】:

  • 如果你想从字面上匹配完整的行,grep 可能会过大。您可以(取决于行数)使用 shell 的内置字符串比较 ([ "$a" = "$b" ])。

标签: linux string bash grep string-matching


【解决方案1】:

更新:在这里使用fgrep -x '/user/hemanth.a' 可能是一个更好的解决方案,因为它避免了必须对$ 等字符进行转义以防止grep 将它们解释为元字符。 fgrep 执行文字字符串匹配而不是正则表达式匹配,-x 选项告诉它只匹配整行。

例子:

> cat testfile.txt
foo
foobar
barfoo
barfoobaz

> fgrep foo testfile.txt
foo
foobar
barfoo
barfoobaz

> fgrep -x foo testfile.txt
foo

原答案

尝试将$ 正则表达式元字符添加到grep 表达式的末尾,如下所示:

echo $snapshottableDirs | tr -s ' ' '\n' | grep -w '/user/hemanth.a$'. 

$ 元字符匹配行尾。

当您使用它时,您可能还想使用匹配行首的^ 元字符,这样grep '/user/hemanth.a$' 就不会意外匹配/user/foo/user/hemanth.a 之类的内容。

所以你会有这个:

echo $snapshottableDirs | tr -s ' ' '\n' | grep '^/user/hemanth\.a$'. 

编辑:您可能实际上并不想要-w,因此我已将其从答案中删除。

编辑 2:@U。 Windl 提出了一个很好的观点。正则表达式中的 . 字符是匹配 any 字符的元字符,因此 grep /user/hemanth.a 最终可能会匹配您不期望的内容,例如 /user/hemanthxa 等。或者更多很可能,它也会匹配/user/hemanth/a 行。要解决这个问题,您需要转义 . 字符。我已经更新了上面的grep 行以反映这一点。

更新:回答您在 cmets 中关于如何转义字符串以便可以在 grep 正则表达式中使用的问题...

是的,您可以对字符串进行转义,以便可以在正则表达式中使用它。我将解释如何做到这一点,但首先我应该说,尝试转义字符串以在正则表达式中使用可能会变得非常复杂,有很多奇怪的边缘情况。例如,与grep 一起使用的转义字符串不一定与sedawkperl、bash 的=~ 运算符甚至grep -e 一起使用。

最重要的是,如果您从单引号更改为双引号,则可能必须添加另一层转义,以便 bash 正确扩展您的字符串。

例如,如果您想使用 grep 搜索 literal 字符串 grep,则必须转义 [*$ 字符,产生正则表达式:

'foo \[bar]\* baz\$'

但如果出于某种原因您决定将该表达式作为双引号字符串传递给grep,那么您将不得不转义转义。否则,bash 会将其中一些解释为转义。如果你这样做,你会看到这个:

echo "foo \[bar]\* baz\$"
foo \[bar]\* baz$

您可以看到 bash 将 \$ 解释为代表字符 $ 的转义序列,因此吞下了 \ 字符。这是因为通常情况下,在双引号字符串中,$ 是一个开始参数扩展的特殊字符。但它只留下了\[\*,因为[* 在双引号字符串中并不特殊,因此它将反斜杠解释为文字\ 字符。要让这个表达式在双引号字符串中作为 grep 的参数工作,您必须转义最后一个反斜杠:

# This command prints nothing, because bash expands `\$` to just `$`,
# which grep then interprets as an end-of-line anchor.
> echo 'foo [bar]* baz$' | grep "foo \[bar]\* baz\$"

# Escaping the last backslash causes bash to expand `\\$` to `\$`,
# which grep then interprets as matching a literal $ character
> echo 'foo [bar]* baz$' | grep "foo \[bar]\* baz\\$"
foo [bar]* baz$

但请注意,"foo \[bar]\* baz \\$"不会sed 一起使用,因为 sed 使用不同的正则表达式语法,其中转义 [ 会导致它变为 一个元字符,而在grep 中,您必须将其转义以防止它被解释为元字符。

同样,是的,您可以转义文字字符串以用作grep 正则表达式。但是,如果您需要匹配包含需要转义的字符的文字字符串,事实证明有一个更好的方法:fgrep

fgrep 命令实际上只是grep -F 的简写,其中-F 告诉grep 匹配“固定字符串”而不是正则表达式。例如:

> echo '[(*\^]$' | fgrep '[(*\^]$'
[(*\^]$

这是因为fgrep 不知道也不关心正则表达式。它只是在寻找确切的文字字符串'[(*\^]$'。然而,这种方式会让你回到第一方,因为fgrep 将匹配子字符串:

> echo '/users/hemanth/dummy' | fgrep '/users/hemanth'
/users/hemanth/dummy

谢天谢地,考虑到您的具体需求,有一种方法可以解决这个问题,事实证明这可能比我最初的答案更好。 fgrep-x 选项告诉它只匹配整行。请注意,-x 并不特定于fgrep(因为fgrep 实际上只是grep -F)。例如:

> echo '/users/hemanth/dummy' | fgrep -x '/users/hemanth' # prints nothing

这相当于您通过转义 grep 正则表达式得到的结果,并且几乎可以肯定比我之前将您的正则表达式包含在 ^$ 中的答案更好。

现在,正如承诺的那样,以防万一您想走这条路,以下是您如何转义固定字符串以用作grep 正则表达式:

# Suppose we want to match the literal string '^foo.\ [bar]* baz$'
# It contains lots of stuff that grep would normally interpret as
# regular expression meta-characters. We need to escape those characters
# so grep will interpret them as literals.
> str='^foo.\ [bar]* baz$'
> echo "$str"
^foo.\ [bar]* baz$

> regex=$(sed -E 's,[.*^$\\[],\\&' <<< "$str")
> echo "$regex"
\^foo\.\\ \[bar]\* baz\$

> echo "$str" | grep "$regex"
^foo.\ [bar]* baz$
# Success

同样,出于上述原因,我不推荐这种方法,尤其是当fgrep -x 存在时。

【讨论】:

  • 谢谢!这似乎有效!为什么我不需要-w 选项?
  • @Hemanth 有两个原因。首先,使用^$ 锚点,-w 变得无用。其次,因为grep实际上并不认为/这个字符是一个“单词”字符,所以-w的效果可能不是你所期待的。
  • @Hemanth 我给出的不使用-w 的第二个原因实际上是您第一次尝试失败的原因。即grep 看到/user/hemanth.a,然后是/dummy1,并且满足-w 条件,因为/ 不是单词字符。每man grep:“组成单词的字符是字母、数字和下划线。”
  • 有没有办法可以从我试图 grep 的字符串中转义所有特殊字符?我可以将字符串包含在" " 中以实现此目的吗? ex: grep "^/user/hemanth.a$"
  • @Hemanth 引用无济于事。看我的更新。底线:你可能想要fgrep -x "/user/hemanth.a"
【解决方案2】:

阅读man grep中的“锚定”:

   Anchoring
       The caret ^ and the dollar sign $ are meta-characters that respectively
       match the empty string at the beginning and end of a line.

还要注意. 匹配任何字符(来自所述手册页):

The period . matches any single character.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-26
    • 2011-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-11
    • 1970-01-01
    相关资源
    最近更新 更多