有没有一种简单的方法将“原始”字符串传递给grep?

65

grep在命令行中使用时不能接受“原始”字符串,因为有些字符需要转义才能被视为字面量。例如:

$ grep '(hello|bye)' # WON'T MATCH 'hello'
$ grep '\(hello\|bye\)' # GOOD, BUT QUICKLY BECOMES UNREADABLE

我曾使用printf来自动转义字符串:
$ printf '%q' '(some|group)\n'
\(some\|group\)\\n

这将生成一个经过Bash转义的字符串版本,使用反引号可以轻松地将其传递给grep调用:
$ grep `printf '%q' '(a|b|c)'`

然而,它显然不是为此而设计的:输出中的一些字符未被转义,而有些则不必要。例如:

$ printf '%q' '(^#)'
\(\^#\)
^字符在传递给grep时不应该被转义。
是否有一个CLI工具,可以将原始字符串转换为bash转义版本的字符串,并可直接用作grep模式?如果没有,我该如何在纯bash中实现?

5
你想要逃避什么,以及你不想逃避什么并不是很清楚。如果你想查找字面字符串,请使用grep -F命令。 - jordanm
我尝试让问题更清晰,请再看一遍。 - salezica
我还是不明白。你是如何将字符串传递到shell中,以便首先使用printf打印它们?在那个时候,它们已经准备好被传递给printfgrep了。 - ephemient
grep -F \(some\|group\)\\n 显然有效(这就是 %q 整个的意义所在...) - ephemient
显示剩余2条评论
6个回答

71

如果你想要查找一个精确的字符串,

grep -F '(some|group)\n' ...

-F告诉grep将模式视为文本而非正则表达式。

(这通常也可以使用fgrep。)


我尝试让问题更清晰,请再看一下。 - salezica
1
@jordanm 比弃用更为严厉。在POSIX.2中被标记为LEGACY,并且在1997年之后的任何规范中都没有被继续沿用。 - ephemient
2
这完全不是我想表达的 :( - salezica
这并不总是有效的。尝试例如:echo "A-B-C" | grep -F "-B-" - LLL
1
@LLL,这是因为模式“-B-”看起来像一个选项(实际上,“-B”是一个有效的grep选项),你需要使用“--”特殊选项来终止选项解析;即echo A-B-C | grep -F -- -B-(请注意,在这种情况下,您使用的引号是不必要的,并且在调用grep之前它们被shell剥离)。 - kbolino
显示剩余3条评论

34
如果您尝试使grep使用扩展正则表达式语法,那么要做的方法是使用grep -E(也称为egrep)。您还应该了解grep -F(也称为fgrep)以及在新版GNU Coreutils中的grep -P
背景:最初的grep拥有一组相当小的正则表达式运算符;这是Ken Thompson最初的正则表达式实现。稍后开发了一个具有扩展能力的新版本,并因兼容性原因而获得了不同的名称。在GNU grep中,只有一个二进制文件,如果以grep调用,则理解传统的基本RE语法,并且以egrep调用时理解ERE。通过使用反斜杠转义引入特殊含义,可以在grep中使用来自egrep的某些构造。
随后,Perl编程语言进一步扩展了这种形式主义;这个正则表达式方言似乎是大多数新手错误地期望grep支持的东西。使用grep -P,它确实支持;但这在所有平台上都不被广泛支持。
因此,在grep中,以下字符具有特殊含义:^$[]*.\egrep中,以下字符也具有特殊含义:()|+?{}。(重复的大括号不在原始的egrep中。)分组括号还使用\1\2等启用反向引用。
在许多版本的grep中,您可以通过在egrep特殊字符之前放置反斜杠来获得egrep行为。还有像\<\>这样的特殊序列。在 Perl 中,引入了大量的附加转义字符,如 \w\s\d。在 Perl 5 中,正则表达式功能得到了大幅扩展,包括非贪婪匹配 *?+? 等,非捕获括号 (?:...),前瞻和后顾等。
...话虽如此,如果您真的想将 egrep 正则表达式转换为 grep 正则表达式而不调用任何外部进程,可以尝试对每个 egrep 特殊字符使用 ${regex/pattern/substitution};但请注意,这不能正确处理字符类、取反字符类或反斜杠转义。

2
不错的回答。正则表达式是一种强大的工具,但不幸的是,许多命令对它们的实现有所不同。 - glenn jackman
为什么有这么多不同的正则表达式方言?虽然相关,但那里的答案不够详细。 - tripleee
如果您正在尝试在Bash(或更一般的POSIX正则表达式)中使用某些PCRE功能,则可以参考我的答案,其中包含解决方法。也许还可以查看https://dev59.com/n2Ml5IYBdhLWcg3wTlcl。 - tripleee
https://dev59.com/nFsX5IYBdhLWcg3wW-f-#33908887 对Python中正则表达式支持的一些注释,因此可以追溯到Perl和最终Henry Spencer的实现。 - tripleee
非常出色(且正确)地使用了 tooto,甚至是连续使用。做得好 @tripleee - Dubslow

30

当我使用grep -E与用户提供的字符串时,我会用这个来转义它们

ere_quote() {
    sed 's/[][\.|$(){}?+*^]/\\&/g' <<< "$*"
}

例子运行

ere_quote ' \ $ [ ] ( ) { } | ^ . ? + *'
# output
# \\ \$ \[ \] \( \) \{ \} \| \^ \. \? \+ \*

通过这种方式,您可以在正则表达式中安全地插入引用字符串。

例如,如果您想要查找以用户内容开头的每一行,并让用户提供类似于.*的有趣字符串。

userdata=".*"
grep -E -- "^$(ere_quote "$userdata")" <<< ".*hello"
# if you have colors in grep you'll see only ".*" in red

你的 ere_quote 函数中的字符类/集合缺少“/”字符,因此它无法转义。 - fholzer
@fholzer 为什么要在正则表达式中引用“/”?它在正则表达式中没有特殊含义。如果您将其用作分隔符,则可以使用不同的分隔符或对其进行转义,但这与作为正则表达式的一部分进行评估是不同的。 - Riccardo Galli
尽管从技术上讲是正确的,但我的假设是 ere_quote 函数的输出随后将在例如 sed 中使用。虽然可以使用不同的分隔符,但无论选择哪个分隔符都需要进行转义。因此,确实,在一般情况下正则表达式中斜杠没有特殊含义,但值得注意的是,当 ere_quote 输出稍后与某些工具一起在脚本中使用时,如果必要,应该使用这些工具选择的分隔符修改字符类。 - fholzer

7
我认为之前的回答并不完整,因为它们忽略了一个重要的事情,即以短横线(-)开头的字符串。所以,尽管这样做不会起作用:
echo "A-B-C" | grep -F "-B-"

这个将会:
echo "A-B-C" | grep -F -- "-B-"

grep-e选项,这样你就可以明确地传递以破折号开头的模式,而不会产生歧义。这与正则表达式语法本身无关。 - tripleee

2
quote() {
    sed 's/[^\^]/[&]/g;s/[\^]/\\&/g' <<< "$*"
}

用法: grep [OPTIONS] "$(quote [STRING])"

该函数有一些重要的好处:

  • quote 独立于正则表达式风格。您可以在以下情况下使用 quote 的输出:
    • grep (-G)` (BRE,默认值)
    • grep -E (ERE)
    • grep -P (PCRE)
    • sed (-E) "s/$(quote [STRING])/.../"(只要您不使用 \[] 代替 /)。
  • quote 即使在与引号无关的边角情况下也能正常工作,例如:
    • 前导 - 被引用,以防止它们被 grep 误解为选项。
    • 尾随空格被引用,以防止它们被 $(...) 移除。

quote 仅在 [STRING] 包含换行符时失败。但是,一般来说,没有解决此问题的方法,因为诸如 grepsed 的工具可能不支持其搜索模式中的换行符(即使它们被写成 \n)。

此外,引用输出通常比未引用的输入长三倍,这是一个缺点。


0

只是想评论下面的例子,它显示grep将子字符串“-B”解释为命令行选项,导致命令失败。

echo "A-B-C" | grep -F "-B-"

grep有一个特殊的选项来处理这种情况:

-e PATTERNS, --regexp=PATTERNS 将PATTERNS作为模式。如果多次使用此选项或与-f(--file)选项组合使用,则搜索所有给定的模式。此选项可用于保护以“-”开头的模式。

因此,解决此问题的方法是:

echo "A-B-C" | grep -F -e "-B-" -

这似乎基本上重复了LLL在2017年的答案,尽管提供了稍微不同的解决方案。 - tripleee

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