【问题标题】:php pcre regular expressions without delimiterphp pcre 正则表达式不带分隔符
【发布时间】:2021-11-21 20:38:53
【问题描述】:

背景/简介

一般来说,PHP 中的正则表达式,例如对于preg_match(),以/ 之类的分隔符开始和结束。我个人经常使用@

除了分隔符之外,还可以使用 (){}[] 等开括号和右括号。

分隔符 char 需要被转义,当它应该被解释为常规字符时。例如。 preg_match('@^\w+\@\w+\.\w+$@', $mail) 需要将'@' 转义为'\@'

函数preg_quote(string $str, ?string $delimiter) 接受null 作为$delimiter,这表明正则表达式可以以我们不必担心分隔符的方式编写。

使用() 似乎我们不必担心分隔符,因为无论如何'('')' 已经需要转义了。

[]{} 有点不同。虽然孤立的 '[' 会导致错误,但孤立的 ']''{''}' 不会。

动机

对于包开发,我想提供用户可以指定正则表达式片段的方法,而不用担心分隔符的选择。

例如如果我在内部使用'/' 作为分隔符,那么用户(调用代码)需要在提供的正则表达式片段中转义'/'。如果我使用'@',他们可以不转义'/',但需要转义'@'。如果我使用null / '()',他们就不需要逃避任何事情——我想。

这是一个虚构的例子。请不要问->setFragment()是做什么的,你只需要知道第二个参数接收一个正则表达式片段(即可以插入一个正则表达式的sn-p)。

// If regex like '/../':
$system->setFragment('email', '\w+@\w+\.\w+');  // nothing escaped.
$system->setFragment('dir', '\w+(\/\w+)*');  // '/' escaped.

// If regex like '@..@':
$system->setFragment('email', '\w+\@\w+\.\w+');  // '@' escaped.
$system->setFragment('dir', '\w+(/\w+)*');  // nothing escaped.

// If regex like '(..)':
$system->setFragment('email', '\w+@\w+\.\w+');  // nothing escaped.
$system->setFragment('dir', '\w+(/\w+)*');  // nothing escaped.

另一个例子,更类似于我实际在做的事情:

function buildMessageRegex(string $message, ?string $delimiter, array $regex_fragments = []): string {
  $quoted_message = preg_quote($message, $delimiter);
  $regex_body = strtr($quoted_message, $replacements);
  return $delimiter !== null
    ? $delimiter . '^' . $regex_body . '$' . $delimiter
    : '(^' . $regex_body . '$)';
}

// By using $delimiter === null, we don't have to escape '/' or '@'.
$regex = buildMessageRegex('Mail: %mail, Dir: %dir.', null, [
  '%mail' => '\w+@\w+\.\w+',
  '%dir' => '\w+(/\w+)*',
]);

问题

似乎() 是编写正则表达式的唯一方法,我不必担心分隔符,并且可以使用 null 作为分隔符调用preg_quote($str, null)

这个假设正确吗?

如果是这样,我总是可以使用() 作为分隔符,并且不需要在方法中提供分隔符选项。

还是我错过了什么?

范围

我不确定这个问题/问题是否特定于 PHP,或者更普遍地适用于 PCRE 任何使用它的地方(我假设是在 Perl 中?)。

我个人对 PHP 案例很感兴趣,但我认为值得在一个很好的答案中提及这在 PHP 之外是如何应用的。

【问题讨论】:

  • 你为什么打电话给preg_quote()?如果用户提供正则表达式片段,不应该将特殊字符保留在其中吗?如果用户输入应按字面意思匹配,而不是正则表达式,则仅使用 preg_quote()
  • 如果用户不提供分隔符,这意味着您要添加自己的分隔符。因此,无论您使用什么分隔符,在调用preg_quote() 时都可以使用相同的分隔符。我不明白你为什么关心null 案子。
  • 我没有在用户提供的正则表达式片段上调用preg_quote()。对于正则表达式的其他部分,我可能会在流程的其他部分调用它。我只提到它是为了表明 php 确实支持不需要转义分隔符的正则表达式。
  • @Barmar 我在“动机”中添加了一个示例。
  • 没关系,现在我看到你在中间逃脱了@

标签: php regex pcre


【解决方案1】:

比具体回答您的问题更实用的解决方案。

许多字符可用作模式分隔符,包括 ascii 范围内的不可打印字符:SOH、STX、ETX、EOT、ENQ、ACK ...

它们不太可能在字符串中找到,用户在键盘上键入它们的可能性更小(如果用户真的决定在模式中放入 SOH,他可能会使用转义序列 @ 987654321@看东西)。

因此,您可以通过这种方式合理地构建模式(例如使用 SOH):

$pattern = chr(1) . $body . chr(1) . $modifiers;

如果您寻找比 SOH(标题开头 U+0001)更有意义的内容,您最终可以选择控制字符 RS(记录分隔符 U+0030)或 EOT(传输结束 U+0004)。请注意,您不能使用 NUL (U+0000)。

显然,完全可以肯定的是,无论您选择哪种分隔符,总会有这两个很好的旧解决方案:转义或删除它。

【讨论】:

    【解决方案2】:

    不幸的是,您认为 () 总是需要转义的逻辑不正确。它们通常不需要在 [] 内转义,但如果 () 是分隔符,它们就需要。

    例如:

    preg_match('/[(]/', "foo(bar", $match);
    

    有效,但是

    preg_match('([(])', "foo(bar", $match);
    

    得到一个“没有找到匹配分隔符')'的结尾”错误。

    因此,如果您使用() 作为分隔符,调用者将需要转义[] 中的这些字符,这通常不是必需的。

    【讨论】:

    • 啊,我明白了。接得好。所以[]{}() 都不是灵丹妙药。对于preg_quote()null 仍然是这些选择的正确选择,因为无论如何它都会逃脱所有这些选择。 3v4l.org/Zc3PK
    • 许多来回到达本质:)
    猜你喜欢
    • 1970-01-01
    • 2014-08-02
    • 1970-01-01
    • 1970-01-01
    • 2014-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多