Каналы - это функция ОС, которая работает с байтовыми буферами и никак не интерпретирует их содержимое. Таким образом, переданный по конвейеру текст не проходит через 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), который отображается на байт 0x81 в ISO 8859-1, но не может быть закодирован в Windows-1252. - Вы также можете увидеть символ
ƒ
(U + 0192), который соответствует 0x83 в Windows-1252, но не существует вообще в ISO 8859-1.
Так что хитрость заключается в том, чтобы использовать Windows-1252, когда это возможно, и ISO 8859-1 в качестве запасного варианта, определяя индивидуально для каждой кодовой точки. (libiconv может сделать это через 'ICONV_SET_FALLBACKS', но iconv
инструмент CLI не может.) Легко написать свой собственный инструмент:
#!/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"))