unicode / multibyte修饰符和mb_ereg_replace的结果不同 - php

这个正则表达式似乎很成问题:

(((?!a).)*)*a\{

我知道正则表达式很糟糕。这不是这里的问题。

用此字符串测试时:

AAAAAAAAAAAAAA{AA

字母Aa几乎可以替换为任何东西,并导致相同的问题。

此正则表达式和测试字符串对被压缩。完整的示例可以在here中找到。

这是我用来测试的代码:

<?php
$regex = '(((?!a).)*)*a\\{';
$test_string = 'AAAAAAAAAAAAAA{AA';
echo "1:".mb_ereg_replace('/'.$regex.'/','WORKED',$test_string)."\n";
echo "2:".preg_replace('/'.$regex.'/u','WORKED',$test_string)."\n";
echo "3:".preg_replace('/'.$regex.'/','WORKED',$test_string)."\n";

结果可以在这里查看:

http://3v4l.org/Yh6FU

理想的结果是因为正则表达式不匹配,所以返回相同的测试字符串。

当将preg_replaceu修饰符一起使用时,根据以下注释,它的结果应与mb_ereg_replace相同:

php multi byte strings regex

mb_ereg_replace完全可以正常工作。它返回测试字符串,因为正则表达式不匹配。

但是,对于PHP版本4.3.4-4.4.5、4.4.9-5.1.6以外的preg_replace似乎无效。

对于某些PHP版本,结果为错误:

进程退出,代码为139。

对于其他一些PHP版本,结果为NULL
对于其余的,尚未创建mb_ereg_replace

另外,从字符串或正则表达式中仅删除单个字母似乎完全改变了哪些版本的PHP会产生哪些结果。

从此评论来看:

php multi byte strings regex

应该避免使用ereg*,这是有道理的,因为它较慢并且支持的内容少于preg*。这使得不希望使用mb_ereg_replace。但是,没有mb_preg_replace选项,因此这似乎是唯一可行的选项。

所以,我的问题是:
对于给定的字符串和正则表达式对,mb_ereg_replace是否可以替代正常工作?

参考方案

您知道(...)(?:...)之间的区别吗?

(...) ...这定义了标记组。表达式在圆括号内找到的字符串在内部存储在变量中以供反向引用。

(?:...) ...这定义了一个非标记组。括号内的表达式所找到的字符串不在内部存储。这种非标记组通常用于在字符串上多次应用表达式。

现在,让我们看一下您的表达式(((?!a).)*)*a\{,该表达式在文本编辑器UltraEdit中的Perl正则表达式中使用时会导致错误消息The complexity of matching expression has exceeded available resources

(?!a). ...应该在下一个字符不是字母“ a”的地方找到一个字符。好的。但是您想找到一个字符串,该字符串包含0个或多个字符,最多不超过字母“ a”。您的解决方案是:((?!a).)*)

这不是一个很好的解决方案,因为引擎现在在每个字符上都可以提前查找字母“ a”,如果下一个字符不是“ a”,则匹配该字符,将其存储为字符串以供反向引用,然后继续下一个字符。实际上,我什至不知道在标记组上使用乘法器时内部会发生什么,如此处所做的。不得在标记组上使用乘数。更好的是(?:(?!a).)*

接下来,将表达式扩展到(((?!a).)*)*。还有一个带乘数的标记组?

看起来您想要标记不包含字母“ a”的整个字符串。但是在这种情况下,最好使用:((?:(?!a).)*),因为它为内部表达式找到的字符串定义了1个且只有1个标记组。

因此更好的表达方式是((?:(?!a).)*)a\{,因为现在只有1个标记组,而标记组上没有乘数。现在,引擎确切知道要在变量中存储哪个字符串。

([^a]*?)a\{更快,因为此非贪婪否定字符类定义还匹配a{剩余的不包含字母'a'的0个或多个字符的字符串。如果没有必要,应该避免向前看,因为这样可以避免回溯。

我不知道需要使用表达式逐步检查PHP函数mb_ereg_replacepreg_replace的源代码,以找出导致不同结果的确切原因。

但是,表达式(((?!a).)*)*a\{肯定会导致严重的recursion,因为未定义何时停止匹配数据以及临时存储什么。因此,这两个函数(最有可能)从堆栈以及从堆分配越来越多的内存,直到发生堆栈溢出或“可用内存不足”异常。

退出代码139是由未捕获的堆栈溢出引起的分段错误(违反内存边界),或者使用malloc()从堆中分配了更多的内存时返回了NULL,并且忽略了返回值NULL。我想,通过malloc()返回NULL是退出代码139的原因。

因此,区别使得最喜欢两个函数的错误或异常处理。捕获内存异常或对递归迭代进行计数,并对其进行过多退出以防止在内存异常真正发生之前阻止它,这可能是此表达式上行为不同的原因。

在不知道函数mb_ereg_replacepreg_replace的源代码的情况下,很难给出明确的答案,这有什么区别,但是在我看来,这并不重要。

正如Sam在第一条评论中已经报道的那样,表达式(((?!a).)*)*a\{总是导致大量递归。在仅用17个字符的字符串替换期间,超过119000个步骤(=函数调用)是该表达式有问题的强符号。该表达式可用于使函数或整个应用程序(PHP解释器)遇到异常错误处理,但不能用于真正的替换。因此,此表达式对于PHP函数的开发人员来说是不错的选择,它可以测试无限递归中的错误处理,但不适用于真正的替换操作。

引用的PHP沙箱中使用的完整正则表达式:

(?<!<br>)(?<!\s)\s*(\((?:(?:(?!<br>|\(|\)).)*(?:\((?:(?!<br>|\(|\)).)*\))?)*?\))\s*(\{)

很难以这种形式分析此搜索字符串。

因此,让我们看一下搜索字符串,就像将其作为带有缩进的代码片段一样,以更好地理解此表达式中的条件和循环。

(?<!<br>)(?<!\s)\s*
(
   \(
   (?:
      (?:
         (?!<br>|\(|\)).
      )*
      (?:
         \(
         (?:
            (?!<br>|\(|\)).
         )*
         \)
      )?
   )*?
   \)
)
\s*
(\{)

我希望现在可以更轻松地在此搜索字符串中看到递归。同一块有两次,但不是按顺序排列,而是按嵌套顺序排列,是经典递归。

另外,所有表达式(包括可在任何最终字符(\{)之前形成递归的嵌套表达式)都可以使用乘数*或?。这意味着可以存在,但必须不存在。 {的存在是整个搜索字符串的唯一实际条件。其他所有内容都是可选的,由于此搜索字符串中的递归,因此这样做不是很好。

如果完全不清楚从何处开始和停止选择字符,那么对于递归搜索表达式来说是非常糟糕的,因为它会导致无休止的递归直到异常退出。

让我用[A-Za-z]+([a-z]+)这样的简单表达式解释这个问题

大写或小写1个或多个字母,小写后1个或多个字符(并启用区分大小写的搜索)。很简单,不是吗。

但是第二个字符类定义了一个字符集,该字符集是第一个类定义所定义的字符集的子集。这不好。

York这样的字符串的括号中的表达式应标记什么?

orkrk或只是k或什至什么都没有,因为找不到匹配的字符串作为第一个字符类就已经可以匹配整个单词,因此第二个字符类没有剩余?

Perl正则表达式库通过将乘数*和+声明为默认贪婪(除?之外)来解决此类常见问题。在乘法器之后使用,导致相反的匹配行为。这2条附加规则已帮助解决此问题。

因此,此处使用的表达式仅标记k,使用[A-Za-z]+?([a-z]+)标记字符串ork,使用[A-Za-z]+?([a-z]+?)仅标记第一个o

还有另外一条规则:赞成积极的结果而不是消极的结果。此附加规则避免了第一个字符类已经选择了整个单词York

因此解决了部分或完全重叠的字符集的主要问题。

但是,如果将这样的表达式放入递归中并使用lookahead / lookbehind和backtracking使其变得更加复杂,并且不仅通过1个字符,而且甚至通过多个字符进行回溯,会发生什么?

是否仍然清楚地定义了在整个搜索字符串的每个表达式部分中从哪里开始和停止选择字符的位置?

不它不是。

对于没有明确规则的搜索字符串,通过搜索表达式的哪一部分选择搜索字符串的哪一部分,每个结果或多或少是有效的,包括意外的结果。

另外,由于缺少启动/停止条件,函数可能完全无法将表达式应用于字符串并退出异常,因此很容易发生。

对于使用该搜索表达式的人员,在应用搜索字符串时异常退出肯定总是意外的结果。

搜索函数的不同版本可能会在表达式上返回不同的结果,从而使搜索函数遇到异常的函数出口。搜索功能的开发人员不断更改搜索功能的程序代码,以更好地检测和处理搜索表达式,从而导致无限递归,因为这仅仅是一个安全问题。从应用程序的堆栈或整个RAM分配更多或更多内存的正则表达式搜索对于运行该应用程序的整个计算机的安全性,稳定性和可用性是非常成问题的。 PHP主要用于不应该停止工作的服务器上,因为递归内存分配占用了服务器上越来越多的RAM,因为这最终会杀死整个服务器。

这就是为什么根据所使用的PHP版本获得不同结果的原因。

在完整的搜索表达式上,我花了很长时间,让它在示例字符串上运行了好几次。但老实说,我找不到应该找到的内容,而(\{)剩下的表达式应该忽略的内容。

我了解表达式的某些部分,但是为什么搜索字符串中根本没有递归?

(?<!\s)上的\s*后面进行否定查找的目的是什么?

\s*匹配0个或多个空格,因此我无法理解“上一个字符不是空格”的表达目的。从我的角度来看,负面的回望根本没用,只会增加整个表达式的复杂性。而这仅仅是个开始。

我非常确定,您可以用一个更简单的表达式来实现您真正想要的,而没有递归导致异常函数退出,这取决于搜索到的字符串并删除所有或几乎所有回溯步骤。

PHP:不推荐使用password_hash的'salt'选项 - php

我正在使用密码哈希进行注册。我需要手动创建Salt,以下是我使用的代码:$options = [ 'cost' => 11, 'salt' => mcrypt_create_iv(22, MCRYPT_DEV_URANDOM) ]; $password = password_hash( $this->…

PHP-全局变量的性能和内存问题 - php

假设情况:我在php中运行一个复杂的站点,并且我使用了很多全局变量。我可以将变量存储在现有的全局范围内,例如$_REQUEST['userInfo'],$_REQUEST['foo']和$_REQUEST['bar']等,然后将许多不同的内容放入请求范围内(这将是适当的用法,因为这些数据指的是要求自…

PHP strtotime困境 - php

有人可以解释为什么这在我的服务器上输出为true吗?date_default_timezone_set('Europe/Bucharest'); var_dump( strtotime('29.03.2015 03:00', time()) === strtotime('29.03.2015 04:00�…

php-casperjs获取内部文本 - php

我正在为casperjs使用php包装器-https://github.com/alwex/php-casperjs我正在网上自动化一些重复的工作,我需要访问一个项目的innerText,但是我尚不清楚如何从casperjs浏览器访问dom。我认为在js中我会var arr = document.querySelector('label.input…

表情符号插入数据库php后显示为问号 - php

我知道这个问题已经被问过很多次了,我几乎尝试了所有方法,但是情况有所不同,所以请继续阅读!不要投票!正如我所说,我已经尝试了大多数解决方案!我已将utf8mb4用作编码,并将character_set_connection和character_set_database设置为utf8mb4。我网页中的字符集设置为utf8。我使用了PDO,当我打开与数据库的连接…