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

Posted

技术标签:

【中文标题】file_exists() 的 PHP 不区分大小写版本【英文标题】:PHP Case Insensitive Version of file_exists() 【发布时间】:2011-04-27 06:14:12 【问题描述】:

我正在尝试考虑在 php 中实现不区分大小写的 file_exists 函数的最快方法。我最好的办法是枚举目录中的文件并进行 strtolower() 与 strtolower() 比较,直到找到匹配项?

【问题讨论】:

-1 - 这需要澄清。这是否适用于区分大小写的文件系统。如果不是,那么这个问题就是无稽之谈,因为 PHP 的 file_exists() 对于不区分大小写的文件系统上的文件是不区分大小写的。 @Dwza 不,不是。 @felwithe 5 到 9 年后你提出了这个评论......我不明白你的意思 ^^ 【参考方案1】:

我使用来自 cmets 的源代码来创建这个函数。如果找到则返回完整路径文件,否则返回 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;

【讨论】:

总是返回一个完整的文件名不是很好吗?当找到匹配项时,有时会得到一个布尔值,有时是一个有用的路径,这有点奇怪。 如果文件不存在,你会返回什么? O_o 我认为fileExists函数应该返回true,如果文件存在:) 另一方面,我们应该为其他函数设置不区分大小写的变体,例如 fopenfile_get_contents。所以其他函数名(比如searchFile($fn, $ci))在这里会更好。 @Jonathan 抛出异常是最佳实践;-) 但是正如 @vp_arth 所说,就函数名称而言,我希望 truefalse 作为返回值。【参考方案2】:

这个问题是几年前的问题,但它与多个重复链接,所以这里有一个简单的方法。

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

$result = current(preg_grep("/^".preg_quote($filename)."$/i", glob("$path/*")));
获取路径glob中的所有文件 $filename 的 Grep 在任何情况下 i 都不区分大小写 current 返回数组中的第一个文件名

删除current() 以返回所有匹配的文件。这对于区分大小写的文件系统很重要,因为 IMAGE.jpgimage.JPG 都可以存在。

【讨论】:

您应该先转义$filename var,然后将其用作preg_grep 模式,以避免由于文件名中的某些字符(()- 等)导致匹配失败.)。安全声明应该是:$result = current(preg_grep('/'.preg_quote($filename).'$/i', glob("$path/*"))); @fcunited 响应(下面)在我的经验中更好......“......因为如果你测试文件 foo.jpg 并且存在类似 anytext_foo.jpg 的文件,它会返回 true。” $result = current(preg_grep("/\/".preg_quote($filename)."/i", glob("$path/*")));【参考方案3】:

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

【讨论】:

【参考方案4】:

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

【讨论】:

【参考方案5】:

当我们从 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();

【讨论】:

【参考方案6】:

AbraCadaver 的 +7 评分答案不正确,我没有足够的声誉来评论它,所以这里是正确的解决方案,基于他的回答:

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

AbraCadaver 的答案不正确,因为如果您针对文件 foo.jpg 进行测试并且存在 anytext_foo.jpg 之类的文件,它会返回 true。

【讨论】:

就我而言,最好使用“当前”但“计数”。但我的反应比@AbraCadaver 更好 请看一下“^”符号! "preg_grep("/^".... 如果没有 ^,preg_grep 会在任何地方查找文件名。【参考方案7】:

我稍微调整了功能。猜猜这个更好用

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;

【讨论】:

【参考方案8】:

对于纯 PHP 实现,是的。 the comments for the file_exists function中有一个例子。

另一种选择是在不区分大小写的文件系统上运行脚本。

【讨论】:

谢谢!最终在我的回答中使用了这个。【参考方案9】:

我改进了John Himmelman的功能并提出了这个:suppose that i have a catch system \iMVC\kernel\caching\fileCache

function resolve_path($path)

    # check if string is valid
    if(!strlen($path)) return FALSE;
    # a primary check
    if(file_exists($path)) return $path;
    # create a cache signiture
    $cache_sig = __METHOD__."@$path";
    # open the cache file
    $fc = new \iMVC\kernel\caching\fileCache(__CLASS__);
    # check cache file and validate it
    if($fc->isCached($cache_sig) && file_exists($fc->retrieve($cache_sig)))
    
        # it was a HIT!
        return $fc->retrieve($cache_sig);
    
    # if it is ab
    $is_absolute_path = ($path[0] == DIRECTORY_SEPARATOR);
    # depart the path
    $path_parts = array_filter(explode(DIRECTORY_SEPARATOR, strtolower($path)));
    # normalizing array's parts
    $path_parts = count($path_parts)? array_chunk($path_parts, count($path_parts)) : array();
    $path_parts = count($path_parts[0])?$path_parts[0]:array();
    # UNIX fs style
    $resolved_path = $is_absolute_path ? DIRECTORY_SEPARATOR : ".";
    # WINNT fs style
    if(string::Contains($path_parts[0], ":"))
    
        $is_absolute_path = 1;
        $resolved_path = $is_absolute_path ? "" : ".".DIRECTORY_SEPARATOR;
    
    # do a BFS in subdirz
    foreach ($path_parts as $part)
    
        if (!empty($part))
        
            $target_path = $resolved_path.DIRECTORY_SEPARATOR.$part;
            if(file_exists($target_path))
            
                $resolved_path = $target_path;
                continue;
            
            $files = scandir($resolved_path);

            $match_found = FALSE;

            foreach ($files as $file)
               
                if (strtolower($file) == $part)
                
                    $match_found = TRUE;
                    $resolved_path = $resolved_path.DIRECTORY_SEPARATOR.$file;
                    break;
                
            
            if (!$match_found)
            
                return FALSE;
            
        
    
    # cache the result
    $fc->store($target_path, $resolved_path);
    # retrun the resolved path
    return $resolved_path;

【讨论】:

【参考方案10】:

通过快速谷歌找到此页面后,我使用了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,因此该标志似乎是多余的。我还希望,如果您正在做任何超出琐碎脚本的事情,您希望将其变成一个类以更好地控制目录列表缓存,例如重置它,但这超出了我需要的范围,如果你需要它应该是微不足道的。

【讨论】:

【参考方案11】:

我调整的解决方案,独立于操作系统,case-insensitive 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] 模式搜索文件名似乎是更快的解决方案

【讨论】:

【参考方案12】:
//will resolve & print the real filename
$path = "CaseInsensitiveFiLENAME.eXt";
$dir  = "nameOfDirectory";

if ($handle = opendir($dir)) 
 while (false !== ($entry = readdir($handle))) 
     if (strtolower($path) == strtolower($entry))
       echo $entry ;
    
    closedir($handle);

【讨论】:

【参考方案13】:

今天刚遇到这个问题,但不喜欢这里的任何答案,所以我想我会添加我的解决方案(使用 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变量分配正确的大小写。

【讨论】:

【参考方案14】:

其他答案在大型文件系统(需要搜索的大量文件)上可能会占用大量资源。创建所有文件名的临时表(必要时为完整路径)可能很有用。然后对该表进行类似条件搜索以获取实际情况。

SELECT actual_file_name
FROM TABLE_NAME
WHERE actual_file_name LIKE 'filename_i_want'

【讨论】:

以上是关于file_exists() 的 PHP 不区分大小写版本的主要内容,如果未能解决你的问题,请参考以下文章

php7中常量不区分大小写和区分大小写用法

PHP变量名区分大小写

php命名大小问题

PHP大小写问题

PHP类名不区分大小写

PHP 删除重复的单词(不区分大小写)