PHP glob风格匹配

13

简言之,我编写了一个访问控制系统。

该系统的要求之一是通过将规范化/归一化路径与模式进行匹配来检查是否可以访问。

首先想到的是PREG,但问题在于这些模式是基于文件的,即类似于glob()所接受的模式。基本上,它们只是包含?(匹配一个任意字符)或*(匹配任何字符)的模式。

因此,简单来说,我需要在PHP上重新创建 glob() 的匹配功能。

示例代码:

function path_matches($path, $pattern){
    // ... ?
}

path_matches('path/index.php', 'path/*');        // true
path_matches('path2/', 'path/*');                // false
path_matches('path2/test.php', 'path2/*.php');   // true

一个可能的解决方案是将$pattern转换为正则表达式,然后使用preg_match(),但还有其他方法吗?

NB:我不能使用正则表达式的原因是模式将由非程序员编写。


2
为什么要重新创建glob()函数,如果它已经存在于PHP中?http://php.net/manual/en/function.glob.php - Intrepidd
1
嗯,請再次閱讀問題。glob() 與實際路徑一起使用,我需要重新創建其模式匹配功能。我不知道如何在我的情況下使用 glob()(對不存在的路徑)。 - Christian
5个回答

20

使用fnmatch()函数,它似乎可以解决这个问题。


那个方法可以行,但我需要一个在所有系统上都能工作的,而不仅仅是 POSIX。还是要感谢你的尝试。 - Christian
1
此函数自5.3版本起可在Windows上使用。 - Philippe Gerber

6

我认为将其转换为正则表达式是最好的解决方法。你需要做的就是将* 转换为 .*, 将 ? 转换为 ., 并使用 preg_quote. 然而,这并不像看起来那么简单,因为从做事情的顺序上来说它有点像先有鸡还是先有蛋。

我不是很喜欢这个解决方案,但这是我能想到的最好方法:使用一个正则表达式来生成另一个正则表达式。

function path_matches($path, $pattern, $ignoreCase = FALSE) {

  $expr = preg_replace_callback('/[\\\\^$.[\\]|()?*+{}\\-\\/]/', function($matches) {
    switch ($matches[0]) {
      case '*':
        return '.*';
      case '?':
        return '.';
      default:
        return '\\'.$matches[0];
    }
  }, $pattern);

  $expr = '/'.$expr.'/';
  if ($ignoreCase) {
    $expr .= 'i';
  }

  return (bool) preg_match($expr, $path);

}

编辑 添加了大小写敏感选项。

查看它正常工作


我认为这应该是万无一失的。谢谢,戴夫。 - Christian
“See it working”链接跳转到一个页面,只显示了一个PHP错误。 - apaderno
这真的很不错。然而,它不能正确地工作。例如,*.js会被转换为"/.*\.js/"。看起来没问题,但实际上并非如此。生成的正则表达式将匹配abc.jsx,这可能不是用户想要的。更糟糕的是,生成的正则表达式将匹配abc/def.js。 - Peter Schaeffer
你需要在生成的正则表达式周围添加 ^$ 吗? 通常,Glob 在开头和结尾都有锚定符号,而正则表达式默认没有。 - jlh
不得不在生成的正则表达式周围添加^$,并修改了'\ *':return '[^ \ /] *';,以避免进入子文件夹。 - Maksym

5

PHP已经有一个函数,自从PHP 4.3.0版本以来就包含了它。

fnmatch() 检查传递的字符串是否与给定的shell通配符模式匹配。


3

以下是glob()函数的PHP文档,但我认为preg_match无论如何都是最好的解决方案。

http://php.net/manual/zh/function.glob.php

<?php   
function match_wildcard( $wildcard_pattern, $haystack ) {
   $regex = str_replace(
     array("\*", "\?"), // wildcard chars
     array('.*','.'),   // regexp chars
     preg_quote($wildcard_pattern)
   );

   return preg_match('/^'.$regex.'$/is', $haystack);
}

$test = "foobar and blob\netc.";
var_dump(
    match_wildcard('foo*', $test),      // TRUE
    match_wildcard('bar*', $test),      // FALSE
    match_wildcard('*bar*', $test),     // TRUE
    match_wildcard('**blob**', $test),  // TRUE
    match_wildcard('*a?d*', $test),     // TRUE
    match_wildcard('*etc**', $test)     // TRUE
);
?>

1
是的,我考虑过使用str_replace()方法,但我还不能完全理解它在所有情况下是否安全。另外,你需要将preg_match()的返回值转换为布尔值,但这只是一个小问题。 - DaveRandom
这就是 preg_quote 的作用。它会使整个字符串成为正则表达式安全的。然后它又会使 * 和 ? 变得不安全。所以,是的,它可以做到你想要的,并且不能被滥用。 - Hugo Delsing
我知道这一点,我不喜欢后续的\*\?替换,特别是因为主题字符串/模式字符串可能合法地包含反斜杠。另外,我刚刚注意到,您没有指定分隔符“/”。 - DaveRandom
我认为Dave的担忧是合理的。如果有一个更明确的方法,我会更喜欢这个方法而不是一个更复杂的正则表达式。 - Christian
你可以随时将 * 替换为类似 [a-zA-Z0-9\] 的内容。 - Hugo Delsing
很遗憾,“编辑队列已满”。这个代码不安全。分隔符需要特殊处理,将preg_quote($wildcard_pattern)表达式替换为preg_quote($wildcard_pattern, '/') - geek-merlin

0

我认为这应该适用于将全局模式转换为正则表达式模式:

function glob2regex($globPatt) {
    return '/'.preg_replace_callback('/./u', function($m) {
        switch($m[0]) {
            case '*': return '.*';
            case '?': return '.';
        }
        return preg_quote($m[0],'/');
    }, $globPatt).'\z/AsS';
}

如果你想防止 * 匹配目录名,那么你可能想使用 [^\\/]* 代替 *


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