Ren*_*ger 4 ubuntu perl encoding character-encoding
这是我希望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;
}
Run Code Online (Sandbox Code Playgroud)
该脚本首先创建一个带有常量名称的文件filename。(运行脚本后,我可以使用验证文件的存在,ls并且不使用“有趣”字符创建文件。)
然后,脚本将遍历当前工作目录found中的文件,并打印是否存在名称与刚创建的文件相同的文件。显然应该是这样。
但是,事实并非如此(Ubuntu,bash,LANG=en_US.UTF8)
如果将常数更改为Barlauch,它将按预期方式工作并打印found。
取消注释$filename_read = encode('utf8', $filename_read);不会更改行为。
请问对此有什么解释,我该怎么做才能识别其中包含Umlaute的文件名?
改述的问题(按照我的解释)是:
为什么不
readdir返回新创建的文件名?(此处由filename设置为的变量表示Bärlauch)。
(注意:这filename是一个Perl常数变量,因此这就是它缺少$前面的标记的原因。)
背景:
首先要注意:由于use utf8程序开头的语句filename,由于它包含非ASCII字符,因此将在编译时升级为Unicode字符串。从utf8编译指示的文档中:
启用utf8编译指示具有以下效果:源文本中不在ASCII字符集中的字节将被视为文字UTF-8序列的一部分。这包括大多数文字,例如标识符名称,字符串常量和常量正则表达式模式。
并且,根据perluniintro部分“ Perl的Unicode模型”:
一般原则是Perl尝试将其数据尽可能长地保留为八位字节,但是一旦无法避免Unicodeness,则将数据透明升级为Unicode。
...
在内部,Perl当前使用平台的本机八位字符集(例如Latin-1)(默认为UTF-8)来编码Unicode字符串。
中的非ASCII字符filename是字母ä。如果您使用ISO 8859-1扩展ASCII编码(Latin-1),则将其编码为字节值0xE4,请参见位于的表ascii-code.com。但是,如果从中删除了该ä字符filename,则该字符将仅包含ASCII字符,因此即使您使用了utf8编译指示,也不会在内部将其升级为Unicode 。
所以filename现在是一个Unicode字符串与内部UTF-8标志设置(见UTF8的更多信息,编译UTF-8标志)。请注意,该字母ä在UTF-8中编码为两个字节0xC3 0xA4。
写入文件:
写入文件时,文件名会怎样?如果filename是Unicode字符串,它将被编码为UTF-8。但是,请注意,不必先编码filename(encode_utf8( filename ))。有关更多信息,请参见使用Unicode字符创建文件名。因此,文件名以UTF-8编码字节的形式写入磁盘。
读回文件名:
尝试从磁盘读回文件名时,readdir即使文件名包含以UTF-8编码的字节,也不返回Unicode字符串(设置了UTF-8标志的字符串)。它返回二进制或字节字符串,有关字节字符串与字符(Unicode)字符串的讨论,请参见perlunitut。
为什么不readdir返回Unicode字符串?首先,根据
perlunicode部分“何时不发生Unicode”:
在Perl中,仍有很多地方可以将Unicode(以某种编码或另一种编码)作为参数或作为结果接收,或者在Perl中都可以,但事实并非如此。(...)
以下是此类接口。对于所有这些接口,Perl当前(自v5.16.0起)仅假设字节字符串既作为参数又作为结果。(...)
在这种情况下,Perl不尝试解析Unicode角色的一个原因是答案高度依赖于操作系统和文件系统。例如,文件名是否可以采用Unicode以及采用哪种编码方式,并不是完全可移植的概念。(...)
- chdir,chmod,chown,chroot,exec,链接,lstat,mkdir,重命名,rmdir,-stat,symlink,truncate,取消链接,utime,-X
- %ENV
- 全局(又名<*>)
- 打开,opendir,sysopen
- qx(又名反引号运算符),系统
- readdir,readlink
因此readdir返回字节字符串,因为通常不可能先验地知道文件名的编码。有关为什么这不可能的背景信息,请参见例如:
字符串比较:
现在,最后您尝试将读取的文件名$filename_read与变量进行比较filename:
print "found\n" if $filename_read eq filename;
Run Code Online (Sandbox Code Playgroud)
在这种情况下,$filename_read和之间的唯一区别filename
是$filename_read未设置UTF-8标志(这不是Perl内部识别为“ Unicode字符串”的标志)。
现在有趣的是,eq运算符的结果将取决于输入的字节是否$filename_read为纯ASCII。根据编码模块的文档:
在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标志),则将eq两个字符串视为相等。硒对文档中的讨论编码关于这种行为的背景的更多信息。
结论:
因此,如果您相对有信心所有文件名都是UTF-8编码的,则可以通过将返回的字节字符串解码readdir为Unicode字符串(强制设置UTF-8标志)来解决问题:
$filename_read = Encode::decode_utf8( $filename_read );
Run Code Online (Sandbox Code Playgroud)
更多细节
注意:由于Unicode允许相同字符的多种表示形式,因此在中存在两种形式的ä(带有组合DIAERESIS的拉丁文小写字母A)Bärlauch。例如,
在我的平台(Linux)上,UTF-8编码的文件名使用NFC格式存储,但是在Mac OS上,它们使用NFD格式存储。请参阅Encode::UTF8Mac以获取更多信息。这意味着,如果您在Linux机器上工作,例如克隆Mac用户创建的Git存储库,则可以轻松地在Linux机器上获取NFD编码的文件名。因此,Linux文件系统并不关心文件名的编码方式。它只是将其视为字节序列。因此,即使我的Locale是,我也可以轻松编写一个创建ISO-Latin-1编码文件名的脚本"en_US.UTF-8"。当前的语言环境设置只是应用程序的准则,但是,如果应用程序忽略语言环境设置,则没有什么可以阻止它们执行此操作。
因此,如果不确定从返回的文件名readdir是否使用NFC或NFD,则应在解码后始终分解:
use Unicode::Normalize;
print "found\n" if NFD( $filename_read ) eq NFD( filename );
Run Code Online (Sandbox Code Playgroud)
另请参见Perl Unicode Cookbook的 “始终分解和重新组合”部分。
最后,要了解有关语言环境如何在Perl中与Unicode一起工作的更多信息,可以看一下:
| 归档时间: |
|
| 查看次数: |
1323 次 |
| 最近记录: |