这是我希望在执行时打印found
的Perl脚本:
#!/usr/bin/perl
use warnings;
use strict;
use utf8;
use Encode;
use constant filename => 'Bärlauch';
open (my $out, '>', filename) or die;
close $out;
opendir(my $dir, '.') or die;
while (my $filename_read = readdir($dir)) {
# $filename_read = encode('utf8', $filename_read);
print "found\n" if $filename_read eq filename;
}
该脚本首先创建一个名为常量filename
的文件。 (运行脚本后,我可以使用ls
验证文件是否存在,并且文件不是使用"搞笑"字符创建的。)
然后脚本迭代当前工作目录中的文件,如果存在名称等于刚刚创建的文件的文件,则打印found
。显然情况应该如此。
但是,它没有(Ubuntu,bash,LANG=en_US.UTF8
)
如果我将常量更改为Barlauch
,它会按预期工作并打印found
。
取消注释$filename_read = encode('utf8', $filename_read);
不会改变行为。
是否有这方面的解释,我该怎么办才能识别出包含Umlaute的文件名?
答案 0 :(得分:4)
重新提出的问题(正如我解释的那样)是:
为什么没有
readdir
返回新创建的文件名? (此处由变量filename
表示,其设置为Bärlauch
)。
(注意:filename
是一个Perl常量变量,这就是为什么它错过了前面的$
sigil。)
<强>背景强>
首先注意:由于程序开头的use utf8
语句,filename
将在编译时升级为Unicode字符串,因为它包含非ASCII字符。来自utf8编译指示的文档:
启用utf8 pragma具有以下效果:源中的字节 不在ASCII字符集中的文本将被视为存在 文字UTF-8序列的一部分。这包括大多数文字,如 标识符名称,字符串常量和常量正则表达式 图案。
并且,根据perluniintro部分&#34; Perl的Unicode模型&#34; :
一般原则是Perl试图将其数据保持为8位 尽可能长的字节,但只要Unicodeness不能 避免,数据透明地升级到Unicode。
...
在内部,Perl目前使用的是本机8位 平台的字符集(例如Latin-1)是默认的 UTF-8,用于编码Unicode字符串。
filename
中的非ASCII字符是字母ä
。如果使用ISO 8859-1扩展ASCII编码(Latin-1),则将其编码为字节值0xE4
,请在ascii-code.com
处查看此table。
但是,如果从ä
中删除了filename
字符,它将只包含ASCII字符,因此即使您使用了utf8
pragma,它也不会在内部升级为Unicode。 / p>
因此filename
现在是一个设置了内部UTF-8
标志的Unicode字符串(有关UTF-8
标志的更多信息,请参阅utf8 pragma)。请注意,字母ä
以UTF-8编码为两个字节0xC3 0xA4
。
撰写文件:
编写文件时,文件名会怎么样?如果filename
是Unicode字符串,则它将被编码为UTF-8。但请注意,不必先对filename
进行编码(encode_utf8( filename )
)。有关详细信息,请参阅Creating filenames with unicode characters。因此文件名以UTF-8编码的字节写入磁盘。
回读文件名:
当尝试从磁盘读取文件名时,readdir
不会返回Unicode字符串(设置了UTF-8标志的字符串),即使文件名包含以UTF-8编码的字节。它返回二进制或字节字符串,有关字节字符串与字符(Unicode)字符串的讨论,请参阅perlunitut。
为什么没有readdir
返回Unicode字符串?首先,根据
perlunicode部分&#34;当Unicode不发生时&#34; :
仍有许多地方使用Unicode(在某些编码或 另一个)可以作为参数提供或作为结果或两者同时提供 在Perl中,但事实并非如此。 (...)
以下是此类界面。对于所有这些接口Perl 目前(从v5.16.0开始)只假设字节字符串为 论点和结果。 (...)
Perl不尝试解析Unicode角色的一个原因 在这些情况下,答案高度依赖于 操作系统和文件系统。例如,是否 文件名可以是Unicode,也可以是什么样的编码 不完全是便携式概念。 (...)
- chdir,chmod,chown,chroot,exec,link,lstat,mkdir,rename,rmdir, - stat,symlink,truncate,unlink,utime,-X
- %ENV
- glob(又名&lt; *&gt;)
- open,opendir,sysopen
- qx(又名反引号运算符),system
- readdir,readlink
所以readdir
返回字节字符串,因为通常不可能先验地知道文件名的编码。有关无法实现此目的的背景信息,请参阅:
字符串比较:
现在,您最后尝试将读取的文件名$filename_read
与变量filename
进行比较:
print "found\n" if $filename_read eq filename;
在这种情况下,$filename_read
和filename
之间的唯一区别
是$filename_read
没有设置UTF-8标志(它不是Perl内部认可的&#34; Unicode字符串&#34; )。
现在有趣的是eq
运算符的结果将取决于$filename_read
中的字节是否为纯ASCII。根据{{3}}模块的文档:
在Perl中引入Unicode支持之前,
eq
运算符 只是比较了两个标量所代表的字符串。以。。。开始 Perl 5.8,eq
比较两个字符串同时考虑 UTF8标志。...
解码时,生成的UTF8标志处于打开状态 - 除非您可以明确地表示数据。
因此,在您的情况下,eq
将考虑UTF-8
标记,因为$file_name_read
不包含纯ASCII,因此它将
考虑两个字符串不相等。如果$filename_read
和filename
相同且仅包含纯ASCII字节(且filename
仍设置了UTF-8标志,则$filename_read
没有UTF-8标志set),然后eq
会认为两个字符串相等。请参阅Encode文档中有关此行为背景的更多信息的讨论。
<强>结论:强>
因此,如果您相信所有文件名都是UTF-8编码的,那么您可以通过将从readdir
返回的字节字符串解码为Unicode字符串来解决问题中的问题(强制使用UTF-8标志)待定):
$filename_read = Encode::decode_utf8( $filename_read );
更多详情
注意:由于Unicode允许多个相同字符的表示,因此在ä
中存在两种形式的Bärlauch
(LATIN SMALL LETTER A WITH COMBINING DIAERESIS)。例如,
在我的平台(Linux)上,UTF-8编码的文件名使用NFC格式存储,但在Mac OS上,它们使用NFD格式。有关详细信息,请参阅Encode。这意味着如果您在Linux机器上工作,例如克隆由Mac用户创建的Git存储库,您可以轻松地在Linux机器上获取NFD编码的文件名。所以Linux文件系统并不关心文件名的编码方式;它只是将它视为一个字节序列。因此,即使我的语言环境为"en_US.UTF-8"
,我也可以轻松编写一个创建ISO-Latin-1编码文件名的脚本。当前的区域设置只是应用程序的准则,但如果应用程序忽略了区域设置,则无法阻止它们执行此操作。
因此,如果您不确定readdir
返回的文件名是否使用NFC或NFD,则在解码后应始终分解:
use Unicode::Normalize;
print "found\n" if NFD( $filename_read ) eq NFD( filename );
另请参阅Encode::UTF8Mac
部分&#34;始终分解和重组&#34;。
最后,为了更多地了解Locale如何与Perl中的Unicode一起使用,您可以查看: