PHP不区分大小写的file_exists()函数版本

23

我正在尝试想出在PHP中实现不区分大小写的文件存在性检查函数的最快方法。我最好的选择是枚举目录中的文件并使用strtolower()函数进行大小写不敏感的比较,直到找到匹配项吗?


2
-1 - 这需要澄清。这是针对区分大小写的文件系统吗?如果不是,那么这个问题就没有意义,因为PHP的file_exists()在不区分大小写的文件系统上对文件不区分大小写。 - danorton
@Dwza 不是。 - felwithe
@felwithe 五到九年后你才发表这样的评论...我不知道你的意思是什么^^ - Dwza
14个回答

31

我使用了评论中提供的源代码创建了这个函数。如果找到文件,则返回完整路径,否则返回FALSE。

在文件名中不区分大小写处理目录名称。

function fileExists($fileName, $caseSensitive = true) {

    if(file_exists($fileName)) {
        return $fileName;
    }
    if($caseSensitive) return false;

    // Handle case insensitive requests            
    $directoryName = dirname($fileName);
    $fileArray = glob($directoryName . '/*', GLOB_NOSORT);
    $fileNameLowerCase = strtolower($fileName);
    foreach($fileArray as $file) {
        if(strtolower($file) == $fileNameLowerCase) {
            return $file;
        }
    }
    return false;
}

4
总是返回完整的文件名不是很好吗?当找到匹配项时,有时会得到布尔值,有时会得到有用的路径,这有点奇怪。 - Emil Vikström
4
如果文件不存在,你会返回什么?O_o - Jonathan
2
我认为fileExists函数应该返回true,如果文件存在 :) - vp_arth
@Jonathan 抛出异常是最佳实践;-) 然而,正如 @vp_arth 所说,根据函数名称,我期望返回值为 truefalse - hejdav
默认情况下大小写敏感与否取决于操作系统。Windows使用不区分大小写的方式。这意味着,如果我只使用fileExists('path'),那么这段代码在Windows上无法正常工作。 - vee
显示剩余3条评论

12

这个问题已经几年了,但它与多个问题被链接为重复,所以这里有一个简单的方法。

如果在任何情况下都没有找到$path中的$filename,则返回false;如果在任何情况下找到了$filename,则返回glob()返回的第一个文件的实际文件名:

$result = current(preg_grep("/^".preg_quote($filename)."$/i", glob("$path/*")));
  • 获取路径glob下的所有文件
  • 使用不区分大小写的 i 标志在文件中查找$filename
  • current 返回数组中第一个文件名

移除current() 来返回所有匹配的文件。这在区分大小写的文件系统上非常重要,因为IMAGE.jpgimage.JPG都可能存在。


在将$filename变量用作preg_grep模式之前,您应该对其进行转义,以避免由文件名中的某些字符(()-等)引起的匹配失败。 安全语句应为: $result = current(preg_grep('/'.preg_quote($filename).'$/i', glob("$path/*"))); - Michele DC
@fcunited的回复更好,因为如果你测试文件foo.jpg并且存在类似anytext_foo.jpg的文件,它会返回true。$result = current(preg_grep("/\/".preg_quote($filename)."/i", glob("$path/*"))); - Marco Scarnatto

2

当我们从IIS迁移到apache时,我遇到了同样的问题。以下是我编写的代码片段。它可以返回正确的路径字符串或false。

function resolve_path($path)
{
    $is_absolute_path = substr($path, 0, 1) == '/';
    $resolved_path = $is_absolute_path ? '/' : './';
    $path_parts = explode('/', strtolower($path));

    foreach ($path_parts as $part)
    {
        if (!empty($part))
        {
            $files = scandir($resolved_path);

            $match_found = FALSE;

            foreach ($files as $file)
            {
                if (strtolower($file) == $part)
                {
                    $match_found = TRUE;

                    $resolved_path .= $file . '/';
                }
            }

            if (!$match_found)
            {
                return FALSE;
            }
        }
    }

    if (!is_dir($resolved_path) && !is_file($resolved_path))
    {
        $resolved_path = substr($resolved_path, 0, strlen($resolved_path) - 1);
    }

    $resolved_path = $is_absolute_path ? $resolved_path : substr($resolved_path, 2, strlen($resolved_path));

    return $resolved_path;
}

$relative_path = substr($_SERVER['REQUEST_URI'], 1, strlen($_SERVER['REQUEST_URI']));
$resolved_path = resolve_path($relative_path);

if ($resolved_path)
{
    header('Location: http://' . $_SERVER['SERVER_NAME'] . '/' . $resolved_path);
    die();
}

2

AbraCadaver的回答得到了+7的评分,但是他的回答是不正确的。我没有足够的声望在他的回答下发表评论,所以在此提供一个基于他的回答的正确解决方案:

Original Answer翻译成"最初的回答"

$result = count(preg_grep('/\/'.preg_quote($filename)."$/i", glob("$path/*")));

AbraCadaver的回答是不正确的,因为它在测试文件foo.jpg时返回true,而且如果存在类似于anytext_foo.jpg的文件,则也会返回true。最初的回答。

在我的情况下,最好使用“current”而不是“count”。但它比@AbraCadaver的响应更好。 - Marco Scarnatto
请注意 "^" 符号! 没有 "^",preg_grep 会在任何地方查找文件名。 - Frank

2
在Unix中,文件名是区分大小写的,因此您无法进行不区分大小写的存在性检查,除非列出目录内容。

2

你的方法可行。
或者你可以使用glob在数组中获取当前工作目录中所有文件和目录的列表,使用array_mapstrtolower应用于每个元素,然后使用in_array检查是否存在(在应用strtolower之后)与你的文件名相同的元素。


1
我对这个函数进行了微调,应该更适合使用了。
function fileExists( $fileName, $fullpath = false, $caseInsensitive = false ) 
{
    // Presets
    $status         = false;
    $directoryName  = dirname( $fileName );
    $fileArray      = glob( $directoryName . '/*', GLOB_NOSORT );
    $i              = ( $caseInsensitive ) ? "i" : "";

    // Stringcheck
    if ( preg_match( "/\\\|\//", $fileName) ) // Check if \ is in the string
    {
        $array    = preg_split("/\\\|\//", $fileName);
        $fileName = $array[ count( $array ) -1 ];
    }

    // Compare String
    foreach ( $fileArray  AS $file )
    {
        if(preg_match("/{$fileName}/{$i}", $file))
        {
            $output = "{$directoryName}/{$fileName}";
            $status = true;
            break;
        }
    }

    // Show full path
    if( $fullpath && $status )
        $status = $output;

    // Return the result [true/false/fullpath (only if result isn't false)]
    return $status;
}

0

今天刚看到这个,但不喜欢这里的任何答案,所以我想加入我的解决方案(使用SPL和正则表达式迭代器)

function _file_exists( $pathname ){
    if(file_exists($pathname)) return $pathname;

    try{
        $path = dirname( $pathname );
        $file = basename( $pathname );

        $Dir = new \FilesystemIterator( $path, \FilesystemIterator::UNIX_PATHS );
        $regX = new \RegexIterator($Dir, '/(.+\/'.preg_quote( $file ).')$/i', \RegexIterator::MATCH);

        foreach ( $regX as $p ) return $p->getPathname();

    }catch (\UnexpectedValueException $e ){
        //invalid path
    }
    return false;
}

我使用它的方式是这样的:
 $filepath = 'path/to/file.php';

 if( false !== ( $filepath = _file_exists( $filepath ))){
      //do something with $filepath
 }

这样做将首先使用内置的方法,如果失败则使用不区分大小写的方法,并为$filepath变量分配适当的大小写。


0

通过快速谷歌搜索找到了这个页面,我使用了Kirk的解决方案,但是如果在同一个目录上多次调用它,或者在文件很多的目录上调用它,它会变得很慢。这是因为每次都要循环遍历所有文件,所以我对其进行了一些优化:

function fileExists($fileName) {
    static $dirList = [];
    if(file_exists($fileName)) {
        return true;
    }
    $directoryName = dirname($fileName);
    if (!isset($dirList[$directoryName])) {
        $fileArray = glob($directoryName . '/*', GLOB_NOSORT);
        $dirListEntry = [];
        foreach ($fileArray as $file) {
            $dirListEntry[strtolower($file)] = true;
        }
        $dirList[$directoryName] = $dirListEntry;
    }
    return isset($dirList[$directoryName][strtolower($fileName)]);
}

我放弃了标志以检查大小写不敏感性,因为我假设如果您不需要此行为,您只会使用file_exists,所以该标志似乎是多余的。我还期望,如果您正在执行超出简单脚本的任何操作,您将希望将其转换为类,以获得更多控制目录列表缓存的功能,例如重置它,但这超出了我所需的范围,如果您需要它,应该很容易实现。


0

我的优化解决方案,不受操作系统限制,不区分大小写的realpath()替代方案,覆盖整个路径,命名为realpathi()

/**
 * Case-insensitive realpath()
 * @param string $path
 * @return string|false
 */
function realpathi($path)
{
    $me = __METHOD__;

    $path = rtrim(preg_replace('#[/\\\\]+#', DIRECTORY_SEPARATOR, $path), DIRECTORY_SEPARATOR);
    $realPath = realpath($path);
    if ($realPath !== false) {
        return $realPath;
    }

    $dir = dirname($path);
    if ($dir === $path) {
        return false;
    }
    $dir = $me($dir);
    if ($dir === false) {
        return false;
    }

    $search = strtolower(basename($path));
    $pattern = '';
    for ($pos = 0; $pos < strlen($search); $pos++) {
        $pattern .= sprintf('[%s%s]', $search[$pos], strtoupper($search[$pos]));
    }
    return current(glob($dir . DIRECTORY_SEPARATOR . $pattern));
}

使用glob模式[nN][aA][mM][eE]搜索文件名似乎是更快的解决方案。


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