glob() 在 Windows 上找不到包含多字节字符的文件名?



我正在编写一个文件管理器,需要扫描目录并处理可能具有多字节字符的重命名文件。我在Windows/Apache PHP 5.3.8上本地工作,在目录中使用以下文件名:

  • filename.jpg
  • имяфайлаjpg
  • file filename .jpg
  • פילענאַמעjpg

在实时UNIX服务器上进行测试工作正常。在Windows上使用glob('./path/*')进行本地测试只返回第一个filename.jpg

使用scandir(),至少返回正确数量的文件,但我得到像?????????.jpg这样的名称(注意:那些是常规问号,而不是字符。

我最终需要编写一个"搜索"功能,递归地在整个树中搜索匹配模式或具有特定文件扩展名的文件名,我认为glob()将是合适的工具,而不是扫描所有文件并在应用程序代码中进行模式匹配和数组构建。如果需要的话,我愿意接受其他建议。

假设这是一个常见的问题,我立即搜索谷歌和堆栈溢出,甚至没有找到任何相关的。这是Windows的问题吗?PHP的缺点吗?解决办法是什么?我能做些什么吗?

附录:不确定这有多相关,但file_exists()也为这些文件返回FALSE,传递完整的绝对路径(使用notepad++, php文件本身是UTF-8编码,没有BOM)。我确信路径是正确的,因为没有多字节字符的相邻文件返回TRUE

编辑:glob()可以找到一个名为filename-äöü.jpg的文件。以前在我的.htaccess文件中,我有AddDefaultCharset utf-8,这是我以前没有考虑过的。filename-äöü.jpg打印为filename-���.jpg。删除htaccess行所产生的唯一效果就是文件名可以正常打印了。

我已经完全删除了.htaccess文件,这是我的实际测试脚本的全部(我从原始帖子更改了几个文件名):

print_r(scandir('./uploads/')); 
print_r(glob('./uploads/*'));

Windows本地输出:

Array
(
[0] => .
[1] => ..
[2] => ??? ?????.jpg
[3] => ???.jpg
[4] => ?????????.jpg
[5] => filename-äöü.jpg
[6] => filename.jpg
[7] => test?test.jpg
)
Array
(
[0] => ./uploads/filename-äöü.jpg
[1] => ./uploads/filename.jpg
)

远程UNIX服务器上的输出:

Array
(
[0] => .
[1] => ..
[2] => filename-äöü.jpg
[3] => filename.jpg
[4] => test이test.jpg
[5] => имя файла.jpg
[6] => פילענאַמע.jpg
[7] => 文件名.jpg
)
Array
(
[0] => ./uploads/filename-äöü.jpg
[1] => ./uploads/filename.jpg
[2] => ./uploads/test이test.jpg
[3] => ./uploads/имя файла.jpg
[4] => ./uploads/פילענאַמע.jpg
[5] => ./uploads/文件名.jpg
)

因为这是一个不同的服务器,不管平台配置可能不同,所以我不确定该怎么想,我不能完全把它固定在Windows上(可能是我的PHP安装,ini设置,或Apache配置)。什么好主意吗?

看起来glob()函数取决于你的PHP副本是如何构建的,以及它是否使用unicode感知的WIN32 API编译(我不相信标准构建是)。

Cf。http://www.rooftopsolutions.nl/blog/filesystem-encoding-and-php

节选自文章评论:

菲利普Verdy2010-09-26 8:53 am

在Windows上安装PHP的输出很容易解释:您安装了错误的PHP版本,并且使用了不正确的版本编译为使用Unicode版本的Win32 API。出于这个原因,PHP使用的文件系统调用将使用遗留的"ANSI"API,等等与此版本的PHP链接的C/c++库将首先尝试将utf -8编码的PHP字符串转换为本地"ANSI"代码页(参见前面的CHCP命令)从命令行窗口启动PHP)

你的Windows版本很可能不是这个奇怪的原因事情实际上,这是未编译的PHP版本正确地使用了Win32 API的遗留ANSI版本(用于与Windows 95/98的旧16位版本的兼容性内核中的文件系统支持实际上没有直接支持Unicode,但使用了一个内部转换层将Unicode转换为的实际ANSI版本之前,请先查看本地ANSI代码页API)。

使用编译器选项重新编译PHP以使用的UNICODE版本Win32 API(今天应该是默认的,无论如何总是这样)默认安装在服务器上的PHP永远不会是Windows95或Windows 98…)

那么Windows将能够存储UTF-16编码的文件名(包括在FAT32卷上,即使在这些卷上,它也会生成一个使用文件系统默认的8.3格式的别名短名称(在NTFS卷中可以避免)。

你所描述的都是PHP的问题(不正确的移植到Windows,或运行时错误的系统版本标识):重新阅读随PHP源代码提供的README文件编译标志。我真的认为Windows上的makefile应该能够配置和自动检测,如果它真的需要只使用API的ANSI版本。如果要为服务器编译它,请执行确保Configure脚本将有效地检测到完整的Win32 aPI的UNICODE版本的支持,并将使用它编译PHP和选择要链接的运行库。

我在Windows上使用PHP,正确编译,我绝对不知道你在文章中引用的问题。

让我们永远忘记Win32的这些非unicode版本的本地ANSI代码页不一致Windows图形用户界面,以及文件系统api的OEM代码页,DOS/bios兼容的api,控制台api):这些非unicode版本的api甚至更慢,更昂贵Unicode版本的api,因为它们实际上是翻译的在使用核心Unicode api之前,将代码页转换为Unicode基于Windows nt的内核的情况与基于虚拟DOS扩展程序的Windows版本的情况,例如如Windows 95/98/ME)

当你不使用API的本机版本时,你的API调用将使用通过一个思考层,该层将对两者之间的字符串进行转码Unicode和遗留的ANSI或chcp选择的OEM代码页之一,或者文件系统上提示的OEM代码页:这需要额外的在Win32的非本机版本中临时内存分配API。函数之前需要额外的时间来转换

总结:你在Windows上安装的PHP二进制文件必须是不同的这取决于你是为Windows 95/98/SE(还是旧的)编译它Windows 3的Win16s仿真层。X有一个极小值支持UTF-8,只支持Unicode子集的Unicode使用从DOS启动Windows时选择的ANSI和OEM包扩展程序),或者是否为基于Windows的任何其他版本编译

最能证明这是PHP的问题,而不是Windows的你的奇怪结果不会出现在其他语言中,比如c#,Javascript, VB, Perl, Ruby…PHP在跟踪方面有着非常糟糕的历史版本(和太多的历史源代码的怪癖和错误)今天应该禁用的假设,以及不一致的库它继承了所有的怪癖,这些怪癖最初是在旧版本的PHP的旧版本的Windows,甚至不再是正式的支持,微软甚至PHP本身!)。

换句话说:RTM !或下载并安装二进制版本的PHP的Windows预压缩与正确的设置:我真的认为PHP应该分发已编译的Windows二进制文件Win32 API的Unicode版本的默认值,使用Unicode版本的C/c++库:内部PHP代码将在调用Win32 API之前将其UTF-8字符串转换为UTF-16当检索Win32结果时,从UTF-16返回到UTF-8,而不是将PHP的内部UTF-8字符串转换回本地OEM代码页(用于文件系统调用)或本地ANSI代码页(用于所有其他Win32 api(包括注册表或进程)。

尝试在函数(脚本)中设置内部编码。

setlocale(LC_ALL,'C.UTF-8');

windows上的PHP还不使用Unicode API。所以你必须使用运行时编码(无论它是什么)来处理非ascii字符集。

从PHP 7.1开始,Windows上的长路径和UTF-8路径直接在核心中得到支持。

最新更新