这是一个脚本,用于修复文件从 Windows 移动到 Mac 时损坏的西里尔文文件名(基于对使用不同编码后文件名被乱码后的恢复文件名的回答)
#!/bin/zsh
# Usage: <script> <target directory>
# Requires Perl::Rename
find "$1" -mindepth 1 -print0 |
rename -0 -d -e '
use Unicode::Normalize qw(NFC);
use Encode qw(:all);
if ($_ =~ /[†°Ґ£§•¶І®©™Ђђ≠]/) {
my $check = DIE_ON_ERR | LEAVE_SRC;
my $new = eval {encode("UTF-8",
decode("cp866",
encode("mac-cyrillic",
NFC(decode("UTF-8", $_, $check)), $check), $check))
};
if ($new) {$_ = $new;} else {warn $@;}
}'
我希望它仅重命名目标目录中文件名中至少包含以下字符之一的文件:†°Ґ£§•¶І®©™Ђђ≠
。但由于某种原因,脚本会重命名那里的所有文件:例如,正确的文件名срочно.txt
更改为无意义的ёЁюўэю.txt
。我做错了什么?
我的测试文件夹的路径很简单/Users/john/scripts/test
:没有空格,也没有西里尔字母或特殊字符。
该脚本在 macOS 和 BSD 版本上使用find
。
问题得到解答两天后的更新:Stéphane 的 Chazelas 和 Choroba 的版本对我来说很好用。Terdon 的版本对我来说还不行。
默认情况下,Perl 不接受 UTF-8 编码的源代码。如果您使用 UTF-8 编码的字符,则需要告知 Perl,否则 Perl 会将其视为字节(在本例中,字节 209 匹配)。
另外,你应该使用
-u
选项rename
来告诉 Perl 文件名是 UTF-8 编码的(或者根据需要指定任何其他编码)。因此,编写你的脚本:使用以下 Makefile 进行测试(
fix
是脚本本身):输出:
您正在对未解码的文件名进行匹配,因此在进行匹配之前,您需要进行解码(和
decode("UTF-8", $_, $check)
部分)。NFC()
另外,正如前面提到的,
perl
由于 Unix 默认以 iso8859-1 来解释其代码(或者更确切地说,在字节级别,无需进行任何编码解码),而不是 UTF-8,所以/[†°Ґ£§•¶І®©™Ђђ≠]/
除非你告诉它这些是用 UTF-8 表示的,否则它将无法工作。因此
/[†°Ґ£§•¶І®©™Ђђ≠]/
,这实际上与相同/[\x{E2}\x{80}\x{A0}\x{C2}\x{B0}\x{D2}\x{90}\x{C2}\x{A3}\x{C2}\x{A7}\x{E2}\x{80}\x{A2}\x{C2}\x{B6}\x{D0}\x{86}\x{C2}\x{AE}\x{C2}\x{A9}\x{E2}\x{84}\x{A2}\x{D0}\x{82}\x{D1}\x{92}\x{E2}\x{89}\x{A0}]/
,您会认出这\xe2\x80\xa0
是字符的 UTF-8 编码†
:该正则表达式将匹配任何包含
\xe2
或\x80
或\xa0
等字符的字符串,如果您不对文件名进行任何编码/解码,它将匹配任何字符的编码包含\xe2
、\x80
...字节的字符串,并且数千个字符在以UTF-8编码时包含这样的字节,例如р
(U+0440),其UTF-8编码为0xd1 0x80。use utf8
正如其他人所建议的,它告诉 perl 其代码采用 UTF-8 编码,但这需要在脚本开始时完成。在这里,perl
代码作为常规参数传递给rename
脚本(而不是作为代码参数传递给perl
),并由该脚本作为语句的一部分进行评估eval
,因此use utf8
在那里添加的 不适用。比较:相同于:
这是 的代码点
≠
,已从 UTF-8 正确解码。使用:这是 UTF-8 编码的第一个字节的值
≠
(也是â
(U+00E2) 的代码点,在 iso8859-1 中编码为 0xe2)。使用
-u
没有帮助,因为它与文件名的编码/解码有关,而不是 perl 代码,并且在这里我们不想使用,-u
因为我们想通过检查它是否成功来进行我们自己的编码/解码。在这里,您可以使用
PERL_UNICODE=A rename...
它来告诉脚本的perl
参数A
需要以 UTF-8 编码,或者您可以使用\x{HHHH}
或\N{character name}
来表示这些字符并将代码保留为 ASCII:(我曾经
uconv -x name
得到过那些角色名称,用来uconv -x hex/perl
得到\x{HHHH}
表格)。或者
find
进行匹配(假设find
/fnmatch()
实现可以与多字节字符配合使用)和:
(其中是macos 可能在文件名¹中使用的字符
=\u0338
的分解形式)。≠
或者使用
zsh
glob 代替find
:¹ 并且
NFC()
在 perl 代码中它将转换为其C
组合的F
orm,即解码/编码链将转换为的 ormн
。问题在于你匹配了它,
$_
但没有将其视为 Unicode。你需要先将其解码$_
成 Unicode,然后再匹配。以下代码应该可以正常工作:我使用(
foo.sh
上面的脚本在哪里)进行了测试:和:
这是我自己的版本,有一些额外的调整和一个测试用例。
前:
后:
rename --version:
/Users/john/perl5/bin/rename using File::Rename version 2.02, File::Rename::Options version 2.01