以下是来自文本文件的字符串:
@™TdaŽ®Æ‚êƒ~ƒNƒXƒgƒŒ[ƒgEƒrƒLƒjver1.11d1.d2iƒrƒLƒjƒ‚ƒfƒ‹ver.1.1³Ž®”z•z”Åj
它包含许多非打印字符,并复制到此处:https://pastebin.com/TUG4agN4
使用https://2cyr.com/decode/?lang=en,我们可以确认它翻译为以下内容:
☆Tda 式照相れミクストレート・ビキニver1.11d1.d2(ビキニモデルver.1.1正式配布版)
这是源编码 = SJIS (shift-jis),显示为 Windows-1252。
但是,没有网站,我们如何才能获得相同的结果呢?相关工具是 iconv,但工具链中的某些东西坏了。如果我尝试从源文本文件进行 cat 或将其用作 bash 中的“<”标准输入,链中的其中一个“iconv”会很快出错。如果我从文本编辑器 gedit(将文件读取为 utf-16le)复制上述字符串,或者将其作为 iconv 的输出,并进行 utf16 到 utf8 的转换,那么结果很接近,但仍然是错误的:
@儺da式ニれミクストレ[トEビキニver1.11d1.d2iビキニモデルver.1.1ウ式配布版j
工具链失败的一些证据:
$ cat'utf8.txt'|head -1
@™TdaŽ®Æ‚êƒ~ƒNƒXƒgƒŒ[ƒgEƒrƒLƒjver1.11d1.d2iƒrƒLƒjƒ‚ƒfƒ‹ver.1.1³Ž®”z•z”Å
$ cat'utf8.txt'|head -1| iconv -f utf8 -t utf16
���@�"!Tda}��� ��~�N�X�g�R�[�g�E�r�L�jver1.11d1.d2�i�r�L�j� �f�9 ver.1.1��}� z" z ��j
请注意开头的三个无效字符。
$ cat'utf8.txt'|head -1|iconv -f utf8 -t utf16|iconv -f utf16 -t windows-1252
iconv:位置 2 处的输入序列非法
$ echo "@™TdaŽ®Æ‚êƒ~ƒNƒXƒgƒŒ[ƒgEƒrƒLƒjver1.11d1.d2iƒrƒLƒjƒ‚ƒfƒ‹ver.1.1³Ž®”z•z”Åj"| iconv-f utf8-t utf16
��@"!Tda}�� ��~�N�X�g�R[�gE�r�L�jver1.11d1.d2i�r�L�j� �f�9 ver.1.1�}� z" z �j
注意开头的两个无效字符,以及其他差异。从终端复制的序列与文本编辑器中显示的字符串匹配,通过 find (ctrl-F) 匹配确认,这与 2cyr.com 上给出正确结果的字符串相同。
使用 '|iconv -f utf16 -t windows-1252|iconv -f shift-jis -t utf8' 扩展上面的最后一个命令会给出接近但不正确的结果,而不是像直接链那样出错。
如果我尝试创建一个名为示例字符串的文件并使用工具 convmv 对其进行处理,convmv 会说输出文件名包含“不符合 POSIX 文件系统规范的字符!这可能会导致数据丢失。” 大多数对 UTF-8 无效的文件名不会出现此警告。
是否有任何位序列是 Bash 中的管道无法处理的?如果没有,为什么工具链不起作用?
显然,差异是因为 bash 不会将未打印字符(带有数字的框)粘贴到命令行;也许“readline”无法处理它们?但结果接近表明工具链中的转换顺序是正确的,那么为什么它不起作用呢?
原始文件,其文件名以不同的方式加扰(30 天后过期):https://ufile.io/oorcq
答案1
管道是操作系统的一个功能,它与字节缓冲区一起工作,并且不会以任何方式解释其内容。因此管道文本不会传到 bash 和尤其永远不会通过 'readline'。粘贴为命令行参数的文本会。(是的,readline 和终端都可以过滤掉控制字符作为安全措施。)
您的文件实际上是两种编码的混合,windows-1252
和iso8859-1
,因为它们使用 C1 控制字符块(0x80..0x9F)的方式不同。
- ISO 8859-1 将整个范围用于控制字符,字节 0x80..0x9F 对应于 Unicode 代码点 U+0080..U+009F。
- Windows-1252不能表示 C1 控制字符;它使用此范围的大部分内容作为可打印字符,并且有一些“漏洞” - 即未分配任何内容的字节值(0x81、0x8D、0x8F、0x90、0x9D)。
- 否则,这两种编码在 0x00..0x7F 和 0xA0..0xFF 范围内是相同的。
让我们来看看你的“坏”输入文件的第一行,将其从 UTF-16 解码为 Unicode 文本,并将不可打印的字符转义:
\u0081@\u0081™TdaŽ®\u008FÆ‚êƒ~ƒNƒXƒgƒŒ\u0081[ƒg\u0081EƒrƒLƒjver1.11d1.d2\u0081iƒrƒLƒjƒ‚ƒfƒ‹ver.1.1\u0090³Ž®”z•z”Å\u0081j\n
- 您可以看到
\u0081
(U+0081),它映射到 ISO 8859-1 中的字节 0x81,但不能在 Windows-1252 中编码。 - 您还可以看到符号
ƒ
(U+0192),它映射到 Windows-1252 中的 0x83,但在 ISO 8859-1 中根本不存在。
因此,诀窍是在可能的情况下使用 Windows-1252,并使用 ISO 8859-1 作为后备,并针对每个代码点单独决定。(libiconv 可以通过“ICONV_SET_FALLBACKS”执行此操作,但 CLI 工具iconv
不能。)编写自己的工具很容易:
#!/usr/bin/env python3
with open("/dev/stdin", "rb") as infd:
with open("/dev/stdout", "wb") as outfd:
for rune in infd.read().decode("utf-16"):
try:
chr = rune.encode("windows-1252")
except UnicodeEncodeError:
chr = rune.encode("iso8859-1")
outfd.write(chr)
# outputs shift-jis
请注意,只有一半你的输入文件是错误编码的 Shift-JIS。另一半(英语)完全符合 UTF-16 编码;幸运的是,Shift-JIS 会将其通过,因此不需要手动拆分:
#!/usr/bin/env python3
with open("éΦé╟é▌üEé╓é╚é┐éσé▒éªéΦé⌐.txt", "r", encoding="utf-16") as infd:
with open("りどみ・へなちょこえりか.txt", "w", encoding="utf-8") as outfd:
buf = b""
for rune in infd.read():
try:
buf += rune.encode("windows-1252")
except UnicodeEncodeError:
try:
buf += rune.encode("iso8859-1")
except UnicodeEncodeError:
buf += rune.encode("shift-jis")
outfd.write(buf.decode("shift-jis"))