Кодировки текста в Windows: кракозябры при обмене с Linux

· Обновлено: · · Windows, кракозябры, UTF-8, CP932, Linux, PowerShell, Unicode

История изменений (2 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Исправлена ошибка отображения: строки с вертикальной чертой выводились как таблица, из-за чего справочные ссылки нельзя было нажать. Текст статьи не изменился.
Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619770)

Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.

Го Комура (2026). Кодировки текста в Windows: кракозябры при обмене с Linux. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619770 https://comcomponent.com/ru/blog/2026/03/21/000-windows-text-encoding-mojibake-linux/

DOI (последняя версия)
10.5281/zenodo.21619770
DOI (эта версия)
10.5281/zenodo.21619771

Кракозябры (mojibake) в Windows появляются не потому, что японский язык сложен. Почти всегда причина в том, что одну и ту же последовательность байт прочитали как другую кодировку или результат неверного чтения сохранили уже в другой кодировке.

Особенно это видно на стыке Windows и Linux: в Windows до сих пор живут сразу несколько контекстов — CP932, UTF-8, UTF-16, кодовая страница консоли, различия версий PowerShell, — а Linux чаще исходит из UTF-8. Расхождения, которые раньше не были видны, всплывают сразу все.

Как расходятся предположения Windows и LinuxВ Windows остаются CP932, UTF-16, кодовая страница консоли и различия версий PowerShell, а Linux чаще исходит из UTF-8, поэтому при сочетании двух сред расхождение предположений всплывает наружу.WindowsCP932 / UTF-16 / кодовая страница / версииLinuxСильное предположение UTF-8Расхождение предположений всплывает

Рис. 1: Несколько контекстов, оставшихся в Windows, встречаются с предположением UTF-8 в Linux — и расхождение сразу всплывает.

Это не столько про сложность обработки японского текста, сколько про то, удалось ли согласовать, с каким предположением обрабатываются байты. В статье разберём кодировки Windows с точки зрения «почему появляются кракозябры» и по делу соберём места, где при связке с Linux инцидентов становится больше.

Статья для тех, кто передаёт созданные в Windows CSV, журналы и файлы настроек в Linux — или наоборот — и хочет сам разобрать причину кракозябр и восстановить файл. Знание конкретного языка или фреймворка не требуется. В примерах команд используются iconv и PowerShell.

1. Что нужно понять в первую очередь

Если сразу выписать суть, важны следующие шесть пунктов.

  • Кракозябры — это не проблема «символов», а проблема «как интерпретировали последовательность байт».
  • В Windows сосуществуют мир Unicode и мир устаревших кодовых страниц, и даже на одной машине предположения различаются в зависимости от контекста.
  • В Linux предположение UTF-8 сильное, поэтому смесь с CP932 или UTF-16 из Windows легко даёт инцидент.
  • Стадию, где сломалось только отображение, и стадию, где уже сохранили испорченное содержимое, нужно рассматривать раздельно.
  • Безопаснее для нового текста брать UTF-8 первым вариантом, а существующие legacy-файлы оставлять как есть до явной задачи миграции.
  • Кодировка файла, кодировка редактора, кодовая страница консоли и внутренний формат строк приложения — разные вещи. Если их смешать, расследование теряется.

Фразы «в Windows появились кракозябры» недостаточно, чтобы назвать причину. Как минимум нужно отделить, что именно из перечисленного разошлось.

  • Кодировка самого файла
  • Кодировка в момент сохранения
  • Интерпретация редактора
  • Входная и выходная кодовые страницы консоли
  • Внутренний формат строк приложения
  • Локаль Linux и предполагаемая кодировка
Слои, которые разделяют при диагностике кракозябрОдной фразы «в Windows появились кракозябры» причину не найти: нужно отделить, разошлись ли кодировка самого файла, кодировка при сохранении, интерпретация редактора, кодовая страница консоли, внутренний формат приложения или локаль Linux.В Windows появились кракозябрыЧто разошлось?Кодировка самого файлаКодировка при сохраненииИнтерпретация редактораКодовая страница консолиВнутренний формат строк приложенияЛокаль Linux

Рис. 2: Одного «появились кракозябры» недостаточно, чтобы назвать причину: смотрят, какой из этих шести слоёв разошёлся.

1.1 Термины, которые появятся сразу

Кратко соберём сокращения, которые дальше идут без пояснения.

Термин Полная форма Значение в этой статье
BOM Byte Order Mark Несколько байт в начале файла. Сообщают читателю, какая это кодировка Unicode; для UTF-16 дополнительно показывают порядок байт. В UTF-8 BOM можно ставить, можно не ставить
кодовая страница (code page) - Механизм Windows, в котором номером задано, в какой устаревшей кодировке интерпретировать текст. CP932 японской Windows — один из таких номеров
ANSI - В Windows так называют «активную кодовую страницу в этот момент». Содержимое зависит от среды; в японской среде это CP932
локаль (locale) - Набор умолчаний для языка, региона и кодировки. В Linux задаётся через LANG и LC_ALL и включает кодировку, как в ja_JP.UTF-8
WSL Windows Subsystem for Linux Механизм запуска Linux поверх Windows. Предположения Windows и Linux живут на одной машине, поэтому инциденты из этой статьи здесь особенно вероятны
ETL Extract / Transform / Load Обработка, которая извлекает данные, преобразует их и записывает обратно. По пути файл читают и сохраняют заново, и это становится точкой смены кодировки

Карта знаний этой статьи

Искажение кодировки в Windows возникает из расхождения предпосылок encode и decode: последовательность байтов, сохранённую в CP932, читают как UTF-8 — или наоборот, — и симптомы поломки зависят от направления. У Windows PowerShell значения encoding по умолчанию расходятся между 5.1 и 7 и новее: Out-File пишет UTF-16LE, Set-Content — CP932, и такие различия по путям становятся питательной средой для инцидентов. Сторона Linux по locale читает в предположении UTF-8, поэтому передача CP932 или UTF-8 с BOM ведёт к искажению кодировки и порче данных; особенно это легко случается в средах вроде WSL, где обе стороны живут рядом. Если исходная последовательность байтов ещё есть, её можно восстановить, перечитав и записав в правильном encoding через iconv или PowerShell; если же сохранить уже неверно прочитанное содержимое, восстановить нельзя. Статья рекомендует делать UTF-8 первым кандидатом для новых файлов и явно фиксировать сам encoding как контракт ввода-вывода.

Карта знаний: искажение кодировки Windows и взаимодействие с LinuxСхема того, как симптомы искажения кодировки меняются в зависимости от того, читают ли ту же последовательность байтов как CP932 или как UTF-8; что BOM, UTF-16LE и кодовая страница консоли — отдельные слои; что encoding по умолчанию различается между версиями PowerShell; и связи восстановления через iconv и PowerShell с эксплуатационной политикой, в которой UTF-8 — первый кандидатможет вызватьможет вызватьможет вызватьможет вызватьможет вызватьможет вызватьиспользуетиспользуетиспользуетснижаетснижаетпроверяетсяиспользуетиспользуетможет вызватьрекомендуется длярекомендуется дляmojibake (искажение кодировки)CP932UTF-8UTF-16LEBOM (Byte Order Mark)повреждение данных при пересохранении искажённого текстакодовая страница консолиWindows PowerShell 5.1PowerShell 7iconvPowerShelllocale в LinuxWSL (Windows Subsystem for Linux)encoding как контракт I/Oполитика UTF-8 first для новых файлов

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 17, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

2. Что такое кракозябры на самом деле

Суть кракозябр довольно проста.

  1. Строку encode в какой-то кодировке в последовательность байт
  2. Эту последовательность байт decode в какой-то кодировке обратно в строку
  3. Если предположения encode и decode не совпали, результат читается как другая строка
Совпадение предположений encode и decodeКогда строку encode в байты, а байты decode обратно в строку, при совпадении предположений получается исходная строка, а при расхождении она читается как другая.совпадаютразошлисьСтроку encode в байтыБайты decode обратно в строкуПредположения совпадают?Возвращается исходная строкаЧитается как другая строка

Рис. 3: Суть кракозябр в том, что предположения encode и decode не совпали.

Например, если сохранить в UTF-8, байты будут такими.

E3 81 82

Если прочитать эту последовательность как UTF-8, получится . Если в контексте CP932 — она выглядит как другая строка вроде 縺�. Это и есть кракозябры.

Важно, что здесь не «сломался японский текст», а разошлась интерпретация одних и тех же байт.

Посмотрим и в обратную сторону. Если сохранить в CP932, байты такие.

82 A0

Если эту последовательность пытаться читать как UTF-8, ни 0x82, ни 0xA0 не являются допустимым ведущим байтом UTF-8, поэтому оба превращаются в символ-заменитель и видно что-то вроде ��. Со стороны UTF-8 это вообще не является текстом.

На более длинном примере характер виден лучше. Если сохранить 日本語 в CP932, байты такие.

93 FA 96 7B 8C EA

Если прочитать это как UTF-8, получается что-то вроде ���{��. Здесь важно, что только четвёртый байт 0x7B проходит как ASCII {. Во втором байте CP932 иногда оказывается значение из диапазона ASCII, поэтому в «сломанном» результате всплывают знаки вроде { или \.

То есть симптомы зависят от направления.

Фактические байты Ожидание читателя Как выглядит
UTF-8 CP932 Идут правдоподобные кандзи и катакана, вроде
CP932 UTF-8 Сплошные символы-заменители , иногда вперемешку с ASCII-знаками вроде {

«Если идут нечитаемые кандзи — UTF-8 читают как CP932; если сплошные символы-заменители — CP932 читают как UTF-8» — такую зацепку можно держать в голове, и диагностика идёт быстрее. Эта асимметрия полезна.

2.1 Если сломалось только отображение, ещё можно вернуть

У кракозябр есть стадия, на которой ещё можно вернуть как было. Например, если исходные байты не изменились, файл иногда восстанавливается, если открыть его заново в правильной кодировке.

Опасен обратный сценарий.

  1. Файл в UTF-8 ошибочно читают как CP932
  2. На экране видно что-то вроде 縺�
  3. «Строку, которую видно», сохраняют как есть
  4. Исходные байты UTF-8 пропадают

С этой стадии это уже не сбой отображения, а повреждение данных.

Как сбой отображения превращается в повреждение данныхПока файл UTF-8 ошибочно прочитан как CP932 и на экране видны кракозябры, его ещё можно открыть заново; если видимую строку сохранить как есть, исходные байты UTF-8 пропадают, и это уже повреждение данных.Файл UTF-8 ошибочно читают как CP932На экране видны кракозябрыВидимую строку сохраняют как естьИсходные байты UTF-8 пропадаютДосюда ещё можно открыть заново и вернутьОтсюда это повреждение данных

Рис. 4: Ошибочное чтение ещё обратимо; повреждение данных начинается в момент, когда сохранили неверно прочитанное содержимое.

2.2 Ещё опаснее — свести «непредставимые символы» к узкой кодовой странице

Другой типичный инцидент — когда строку Unicode сводят к устаревшей кодовой странице вроде CP932.

Если в строке есть символы, которых нет в целевой кодовой странице, происходит, например, такое:

  • символ заменяется на ?
  • появляется символ-заменитель
  • символ превращается в похожий, но другой
  • преобразование завершается ошибкой

Этот инцидент нужно оценивать не только по «читается / не читается», а по тому, возвращается ли строка к исходной при преобразовании туда и обратно (round-trip). Однажды потерянный символ потом не восстановить, даже узнав правильную кодировку.

Инцидент при сведении к узкой кодовой страницеЕсли строку Unicode преобразовать в узкую кодовую страницу вроде CP932, символы, которых нет у получателя, заменяются или преобразование падает; round-trip исходную строку не возвращает, и однажды потерянные знаки нельзя восстановить, даже зная правильную кодировку.Строка UnicodeПреобразование в узкую кодовую страницу вроде CP932Символы, которых нет у получателяСтановятся ? или символом-заменителемДругой знак / сбой преобразованияRound-trip исходную строку не возвращаетПотерянные символы восстановить нельзя

Рис. 5: Инцидент сведения к узкой кодовой странице смотрят не по «читается / не читается», а по тому, возвращается ли строка после round-trip.

3. Почему в Windows в этом так легко запутаться

Windows выглядит запутанным не просто потому, что он старый. Мир Unicode и мир устаревших кодовых страниц до сих пор живут рядом.

3.1 В Windows API сосуществуют ветка Unicode и ветка кодовых страниц

В Windows API есть две большие ветки.

  • Семейство W: wide character. Обрабатывает Unicode как UTF-16
  • Семейство A: ветка кодовых страниц, которую называют ANSI

То есть внутри Windows с самого начала есть и «путь Unicode», и «путь текущей активной кодовой страницы». Поэтому даже на одной и той же Windows предположение меняется в зависимости от того, через какой API или какой инструмент прошёл текст.

Две ветки Windows APIВ Windows API с самого начала сосуществуют семейство W, которое обрабатывает Unicode как UTF-16, и семейство A, которое работает через текущую активную кодовую страницу, поэтому предположение зависит от пройденного пути.Windows APIСемейство W (wide character)Семейство A (так называемый ANSI)Unicode обрабатывается как UTF-16Обработка через активную кодовую страницуПредположение зависит от пройденного пути

Рис. 6: Внутри Windows с самого начала есть и путь Unicode, и путь кодовых страниц.

3.2 «Японский текст в Windows» — это не что-то одно

На практике вокруг японского текста в Windows чаще всего смешиваются следующие четыре варианта.

  • CP932: часто встречается в legacy-тексте японской Windows
  • UTF-8: всё чаще встречается в новых текстовых файлах, вебе и cross-platform сценариях
  • UTF-16LE: до сих пор обычен в контексте инструментов и API Windows
  • кодовая страница консоли: отдельный слой, который влияет на ввод-вывод cmd.exe и части консольных инструментов

Здесь важно: chcp 65001 не делает файл UTF-8. Сменить кодовую страницу консоли и то, какие байты лежат в уже существующем файле, — разные вопросы.

chcp 65001 и существующий файл — разные вещиchcp 65001 меняет только кодовую страницу консоли; байты существующего файла не меняются, поэтому настройка консоли и содержимое файла — разные вопросы.Выполнить chcp 65001Меняется кодовая страница консолиБайты существующего файлаНичего не меняетсяКонсоль и файл — разные вопросы

Рис. 7: chcp 65001 меняет только интерпретацию консоли; байты файла остаются прежними.

Legacy-текст японской Windows часто небрежно называют «Shift_JIS», но на практике удобнее держать в уме имя CP932 — так меньше путаницы в разговоре. Как минимум явно видно, что речь про японскую устаревшую кодировку, пришедшую из Windows.

3.3 Имя файла и содержимое файла — разные вопросы

Если в Windows нормально видно японское имя файла, легко решить: «значит, и содержимое в порядке». Здесь как раз опасно.

  • слой обработки пути / имени файла
  • слой чтения содержимого файла
  • слой отображения в консоли

Это три разных слоя.

Например, японский путь может обрабатываться без проблем, но если содержимое сохранено в CP932, а в Linux его читают как UTF-8 — оно ломается. И наоборот: даже если содержимое в UTF-8, при несовпадении кодовой страницы консоли ломается только отображение.

Связь слоёв выглядит так.

Кто пишет, байты файла и независимые читателиФакт — только байты файла; редактор, консоль, внутренности приложения и Linux читают их независимо, и успех на одном читателе не гарантирует остальные.Кто пишетприложение / редактор / скриптБайты файлаэто единственный фактЧитатель A: редакторавтоопределение или заданная кодировкаЧитатель B: консолькодовая страница ввода-выводаЧитатель C: внутри приложениякодировка библиотеки по умолчаниюЧитатель D: LinuxUTF-8 по локалиЛомается только отображениеповторное сохранение превращает это в порчуЛомается только отображениефайл целЛомается результат обработкиуходит дальше по цепочкеdecode error или символ-заменитель

Рис. 8: Факт — только байты файла; редактор, консоль, приложение и Linux читают независимо друг от друга.

Смотреть нужно на два момента. Первый: сломаны байты посередине или читатель справа. Второй: четыре читателя справа независимы, и успех на одном не является гарантией для остальных трёх. «В консоли прочиталось, значит и в редакторе будет нормально» не работает именно из-за этой формы.

3.4 Значения по умолчанию в PowerShell и соседних инструментах тоже не согласованы

В Windows инциденты незаметно копит ещё одно: при, казалось бы, одной и той же «записи текста» выходные байты зависят от пути записи.

Особенно стоит смотреть сюда.

  • У Windows PowerShell 5.1 нет согласованной кодировки по умолчанию
  • Часть cmdlet и перенаправлений создаёт UTF-16LE
  • Другим путём используется активная кодовая страница ANSI
  • Начиная с PowerShell 7 по умолчанию UTF-8 без BOM

То есть одной фразы «текст из PowerShell» недостаточно, чтобы назвать кодировку. Нужно смотреть, какой версии, каким cmdlet и каким путём записи это сделали.

Какие байты даёт каждый путь записи, собрано в about_Character_Encoding на Microsoft Learn. Если выписать только часто используемое, получается так.

Путь записи По умолчанию в Windows PowerShell 5.1 По умолчанию в PowerShell 7
Out-File, >, >> UTF-16LE (с BOM) UTF-8 без BOM
Set-Content, Add-Content (новый или пустой файл) ANSI = активная кодовая страница. В японской среде — CP932 UTF-8 без BOM
Export-Csv ASCII. Не-ASCII символы теряются UTF-8 без BOM
Export-Clixml, New-ModuleManifest UTF-16LE UTF-8 без BOM
New-Item -Type File -Value UTF-8 без BOM UTF-8 без BOM
Start-Transcript UTF-8 with BOM UTF-8 без BOM

Есть разница и на стороне чтения. Когда читают файл без BOM, Get-Content в 5.1 считает его ANSI, а Import-Csv и Select-StringUTF-8. Внутри одной сессии предположения уже разошлись.

На практике бьёт сильнее всего другое: при одной и той же «записи текста» Out-File даёт UTF-16LE, а Set-Content — CP932. «Похожий на двоичный текст с кучей NUL byte» из раздела 4.3 почти всегда приходит из умолчаний > или Out-File.

Асимметрия чтения в Windows PowerShell 5.1Когда файл без BOM читают в Windows PowerShell 5.1, Get-Content считает его ANSI, а Import-Csv и Select-String — UTF-8, так что внутри одной сессии предположения уже разошлись.Файл без BOMЧитают через Get-ContentЧитают через Import-Csv или Select-StringСчитают ANSIСчитают UTF-8Внутри одной сессии предположения расходятся

Рис. 9: В 5.1 у одного и того же файла без BOM ожидаемая кодировка зависит от cmdlet, которым его читают.

Ещё одно: в 5.1 даже при -Encoding UTF8 получается с BOM. Если в 5.1 нужно записать UTF-8 без BOM, пишут со стороны .NET.

# Запись UTF-8 без BOM в Windows PowerShell 5.1
$text = "日本語を含む本文"
[System.IO.File]::WriteAllText(
    "C:\work\output.txt", $text,
    (New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false))

Аргумент UTF8Encoding, равный $false, как раз и есть указание не ставить BOM. В PowerShell 7 тот же результат даёт -Encoding utf8NoBOM.

4. Типичные инциденты при связке с Linux

Не редкость, что то, что внутри одной Windows кое-как работало, ломается, как только в цепочку входит Linux. Причина простая: в Linux предположение UTF-8 сильное.

4.1 Текст, сохранённый в Windows в CP932, Linux читает как UTF-8

Самый частый инцидент.

  • Legacy-приложение Windows или старый процесс эксплуатации пишет CSV / TXT / log в CP932
  • Скрипты и инструменты в Linux по локали читают с предположением UTF-8
  • В итоге decode error, или бессмысленная строка

Виноват здесь не инструмент Linux. Корень в том, что у полученных байт не было договорённости о кодировке.

Linux читает текст CP932 как UTF-8Legacy-приложение или старый процесс Windows пишет CSV или журнал в CP932 и передаёт байты без договорённости о кодировке, а скрипт или инструмент Linux по локали читает с предположением UTF-8 — и получается decode error или символ-заменитель.Legacy-приложение пишет CSV / log в CP932Передают без договорённости о кодировкеLinux по локали читает как UTF-8decode error / символ-заменительКорень — отсутствие договорённости

Рис. 10: Виноват не инструмент Linux, а то, что к байтам не приложили договорённость о кодировке.

4.2 UTF-8 без BOM, сделанный в Linux / VS Code, Windows принимает за ANSI

Бывает и обратный инцидент.

  • В Linux или VS Code делают script / config / text в UTF-8 без BOM
  • Windows PowerShell 5.1 или устаревший инструмент считает файл без BOM кодовой страницей ANSI
  • Ломаются только строки с японским текстом или другими не-ASCII символами

Здесь часто винят UTF-8, но настоящая причина в том, что в цепочке есть читатель, который не умеет правильно распознать UTF-8 без BOM.

UTF-8 без BOM принимают за ANSIФайл UTF-8 без BOM, сделанный в Linux или VS Code, Windows PowerShell 5.1 или устаревший инструмент считает кодовой страницей ANSI, и ломаются только строки с японским текстом и другим non-ASCII.В Linux / VS Code делают UTF-8 без BOMЧитают 5.1 или устаревший инструментФайл без BOM считают стороной ANSIЛомаются только строки с non-ASCIIПричина — читатель, который не распознаёт

Рис. 11: В обратном инциденте причина — читатель, который не умеет распознать UTF-8 без BOM.

4.3 Windows пишет UTF-16LE, а в Linux это «не похоже на текст»

Это тоже встречается часто.

  • Часть вывода Windows PowerShell 5.1 или устаревший инструмент пишет UTF-16LE
  • Текстовые инструменты Linux ожидают однобайтовый поток UTF-8
  • В результате получается «похожий на двоичный текст» с кучей NUL byte

Сам UTF-16LE не плох. Но во многих случаях он плохо стыкуется с предположением «просто отдать это текстовым инструментам Linux».

UTF-16LE в Linux выглядит как двоичные данныеUTF-16LE, который пишет часть вывода Windows PowerShell 5.1 или устаревший инструмент, на текстовых инструментах Linux, ожидающих однобайтовый поток UTF-8, выглядит как двоичный текст с кучей NUL byte.Часть вывода 5.1 или устаревший инструментПишет UTF-16LEТекстовый инструмент Linux ожидает поток по 1 байтуВперемешку NUL byte, выглядит как двоичные данные

Рис. 12: Сам UTF-16LE не плох, но с предположением текстовой обработки Linux он не стыкуется.

4.4 Нестыковки даёт и наличие или отсутствие BOM

BOM — это не сама кодировка, но на практике он заметно влияет.

  • Части инструментов Windows BOM помогает
  • Часть инструментов Linux воспринимает BOM как лишние байты в начале
  • В итоге ломается только начало первого столбца или первой строки, появляется невидимый мусор, расходятся результаты сравнения

Особенно для UTF-8: даже если формально это один и тот же UTF-8, байты с BOM и без BOM — разные вещи. Одной фразы «перешли на UTF-8» для правила эксплуатации мало: решена только половина вопроса.

Нестыковки из-за наличия или отсутствия BOMДаже у одного UTF-8 байты с BOM и без BOM разные: части инструментов Windows BOM помогает, а часть инструментов Linux считает его лишними байтами в начале, поэтому ломается только начало.Даже у одного UTF-8 BOM да / нет — разные байтыЧасть инструментов WindowsЧасть инструментов LinuxС BOM становится прощеСчитают лишними байтами в началеЛомается только начало / появляется невидимый мусор

Рис. 13: «Сделали UTF-8» недостаточно; правило появляется, только когда решили и вопрос BOM.

4.5 Если верить тому, что показывает консоль, легко запутаться

При работе на стыке Windows и Linux опасна ещё и консоль.

  • У консоли Windows есть кодовые страницы ввода и вывода
  • Терминал Linux чаще работает с предположением UTF-8 локали
  • Если путь идёт через WSL, SSH, контейнеры и CI, маршрутов отображения становится больше

В таком состоянии суждения «в консоли прочиталось, значит и с файлом всё в порядке» или «в консоли поехало, значит файл повреждён» легко оказываются ошибочными. Безопаснее проверять отдельно, сломано ли то, что видно на экране, или сломаны сохранённые байты.

4.6 Типичные инциденты таблицей

Ситуация Фактические байты Ожидание читателя Типичный симптом
CSV, который сохранило legacy-приложение Windows CP932 Linux — UTF-8 , decode error, бессмысленный японский текст
Файл, сделанный в Linux / VS Code UTF-8 без BOM Windows PowerShell 5.1 считает ANSI Ломаются только строки с японским текстом
Часть вывода Windows PowerShell 5.1 UTF-16LE или ANSI Linux ждёт текст UTF-8 Примесь NUL byte, поведение как у двоичного файла
Файл UTF-8 with BOM UTF-8 + BOM Unix-инструменты ждут plain UTF-8 Ломается только первый столбец, появляются лишние знаки
Верить только отображению в консоли У файла и консоли разные предположения Исследователь судит только по экрану Промах в диагностике причины

5. Диагностику кракозябр ведём этими четырьмя вопросами

Если диагностика кракозябр зашла в тупик, быстрее всего вернуться к следующим четырём вопросам.

5.1 Что представляют собой исходные байты

Сначала смотрят «какие байты сейчас в этом файле». Нужна установка смотреть на байты, а не на внешний вид.

  • Это UTF-8?
  • UTF-8 with BOM?
  • CP932?
  • UTF-16LE?
  • Не пересохранили ли файл по пути, так что он стал уже другим?

5.2 Кто первым записал и с каким предположением

Дальше определяют, кто записал файл первым.

  • Legacy-приложение Windows?
  • PowerShell 5.1 или 7?
  • Скрипт Linux?
  • VS Code?
  • Экспорт из Excel?
  • Какой-то middleware / batch / CI?

Пока это размыто, оценка кодировки превращается в лотерею.

5.3 Кто сейчас читает и с каким предположением

Нужен не только тот, кто записал, но и предположение читателя.

  • Редактор делает auto-detect?
  • PowerShell смотрит на BOM?
  • Linux по локали считает файл UTF-8?
  • Библиотека берёт кодировку по умолчанию?
  • Явно заданы Encoding.UTF8 или cp932?

Кракозябры почти всегда возникают именно здесь.

5.4 Неверно прочитанное содержимое уже сохранили?

Наконец проверяют, остановился ли ущерб на отображении.

  • Байты ещё исходные?
  • Кто-то сохранил содержимое, которое выглядит сломанным?
  • В diff не попали ? или ?
  • Весь текст не переписали в другой кодировке?

Если эти четыре вопроса закрыты, причина, как правило, видна.

Четыре вопроса диагностики кракозябрЕсли по порядку закрыть, что за исходные байты, кто и с каким предположением записал первым, кто и с каким предположением читает сейчас и сохранено ли уже неверно прочитанное, причина кракозябр обычно видна.Вопрос 1: что за исходные байтыВопрос 2: кто первым записал и с каким предположениемВопрос 3: кто сейчас читает и с каким предположениемВопрос 4: неверно прочитанное уже сохранили?Причина видна

Рис. 14: Если диагностика зашла в тупик, быстрее всего вернуться к этим четырём вопросам и закрывать их по порядку.

6. Как чинить сломанный файл

Когда причина видна, следующий шаг — восстановление. Первым делом решают, остались ли ещё исходные байты.

  • Исходные байты на месте: читают заново в правильной кодировке и записывают в целевую кодировку — так возвращают. Это процедура этой главы
  • Результат неверного чтения уже сохранили: остаётся только откат из резервной копии или истории Git. Символы, ставшие или ?, не восстановить, даже если правильная кодировка уже известна

Поэтому самое первое действие — снять копию того, с чем работаете. Преобразование делают по копии, исходный файл не трогают.

Первое ветвление восстановленияСначала снимают копию и проверяют, остались ли исходные байты: если да — читают заново в правильной кодировке и записывают; если результат неверного чтения уже сохранили, остаётся только резервная копия или история Git, а символы-заменители не восстановить.на местеуже сохранили и потерялиСначала снять копиюИсходные байты ещё на месте?Прочитать заново в правильной кодировке и записатьВернуть из резервной копии или истории GitСимволы, ставшие заменителями, не восстановить

Рис. 15: Восстановление начинают со снятия копии, а дальше путь зависит от того, остались ли исходные байты.

6.1 Если вы в Linux — iconv

Чтобы сделать файл CP932 файлом UTF-8, самый прямой путь — iconv.

# CP932 -> UTF-8
iconv -f CP932 -t UTF-8 input.csv > output.csv

Если кодировка источника указана неверно, преобразование останавливается примерно так.

iconv: illegal input sequence at position 0

Сам факт остановки — информация «это не та кодировка», поэтому быстрее перебирать кандидатов. Если же не останавливается ни на чём, файл, возможно, состоит только из ASCII.

Чтобы свести UTF-16LE к UTF-8, берут -f UTF-16LE. Но если файл с BOM явно преобразовать как -f UTF-16LE, BOM иногда остаётся в выводе как один символ U+FEFF. Если обработку BOM хотите отдать iconv, используйте -f UTF-16 и после преобразования обязательно проверьте первый символ.

6.2 Если вы в Windows — PowerShell

Начиная с PowerShell 6.2 в -Encoding можно передать номер кодовой страницы напрямую. Для CP932 это 932.

# PowerShell 6.2 и новее. CP932 -> UTF-8 без BOM
Get-Content -Path .\input.csv -Encoding 932 |
    Set-Content -Path .\output.csv -Encoding utf8NoBOM

Обратно: если файл UTF-8, сделанный в Linux, нужно отдать получателю, который читает только CP932, делают так.

Get-Content -Path .\input.csv -Encoding utf8 |
    Set-Content -Path .\output.csv -Encoding 932

У этой записи есть два побочных эффекта.

  1. Get-Content режет по строкам, Set-Content заново ставит перевод строки после каждой. То есть символы перевода строки приводятся к одному виду
  2. Даже если в исходном файле в конце не было перевода строки, в выводе он появится

Если нужно сохранить байты вместе с переводами строк, файл обрабатывают целиком, не режа на строки.

# CP932 -> UTF-8 без BOM, переводы строк без изменений
$text = [System.IO.File]::ReadAllText(
    "C:\work\input.csv", [System.Text.Encoding]::GetEncoding(932))
[System.IO.File]::WriteAllText(
    "C:\work\output.csv", $text,
    (New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false))

GetEncoding(932) в Windows PowerShell 5.1 работает как есть. Если в PowerShell 7 в вашей среде это даёт исключение, один раз выполните следующее и повторите.

[System.Text.Encoding]::RegisterProvider(
    [System.Text.CodePagesEncodingProvider]::Instance)

Ставить BOM или нет, как в 7.1, решают под получателя. В примере выше аргумент UTF8Encoding, равный $true, даёт вариант с BOM.

6.3 Что обязательно смотреть после преобразования

Преобразование не заканчивается на «ошибки не было». Как минимум смотрят следующее.

  • Читаются ли 2–3 характерные строки с японским текстом
  • Не прибавилось ли ? или . Если прибавилось, эти символы уже потеряны
  • Не изменилось ли число строк
  • BOM и символ перевода строки совпадают с ожиданием получателя
  • Не изменился ли размер файла слишком резко. При переходе с CP932 на UTF-8 японские фрагменты растут с 2 байт до 3 байт, поэтому небольшое увеличение — норма

Особенно важен второй пункт. Когда UTF-8 с символами, которых нет в CP932, сводят к CP932, информация теряется обязательно. Как в 2.2, проверяйте и то, возвращается ли текст после round-trip.

Что обязательно смотреть после преобразованияПреобразование не заканчивается на отсутствии ошибки: проверяют, читаются ли характерные японские строки, не прибавилось ли ? и символов-заменителей, совпадают ли число строк, BOM, перевод строки и размер, и если заменителей стало больше — эти символы уже потеряны.Преобразование завершилось без ошибкиПрочитать 2–3 характерные японские строкиНе прибавилось ли ? и символов-заменителейПроверить число строк, BOM, перевод строки, размерЕсли прибавилось — эти символы уже потеряны

Рис. 16: «Ошибки не было» преобразования не закрывает; проверка содержимого входит в тот же набор.

6.4 Массовое преобразование выносят в «задачу миграции»

Наконец, про эксплуатацию. Починить один сломанный файл и перевести весь репозиторий с CP932 на UTF-8 — разная работа. Второе, как в 7.2 следующей главы, не делают заодно с обычными правками функций: планируют как отдельную задачу миграции.

7. Правила работы, которые снижают число инцидентов

Дальше — практическая часть. В проектах на стыке Windows и Linux число инцидентов заметно падает, если следующие правила решить заранее.

7.1 Для новых файлов UTF-8 — первый вариант

Для нового текстового файла разумно первым вариантом брать UTF-8. Но на этом останавливаться нельзя: нужно решить и как быть с BOM.

Удобно решать так.

  • Текст, который чаще читают в Linux: базово UTF-8 без BOM
  • Скрипты, которые читают устаревшие инструменты Windows или Windows PowerShell 5.1: наличие или отсутствие BOM явно задавать под получателя
  • Если есть явный получатель, которому нужен UTF-16LE, это требование записывают в спецификацию

Если написать только «унифицировали на UTF-8», позже поссорятся из-за BOM.

Как выбирать кодировку нового файлаДля нового текстового файла UTF-8 берут первым вариантом и на этом не останавливаются: если чаще читает Linux — базово UTF-8 без BOM, если устаревший инструмент или Windows PowerShell 5.1 — BOM явно под получателя, если нужен UTF-16LE — требование пишут в спецификацию.чаще Linux5.1 или устаревший инструментполучатель, которому нужен UTF-16LEНовый текстовый файлUTF-8 как первый вариантКто будет читать?Базово UTF-8 без BOMBOM явно под получателяТребование записать в спецификацию

Рис. 17: Для нового файла UTF-8 берут первым вариантом и доводят решение по BOM до требований получателя.

7.2 Существующие legacy-файлы держат как есть до явной задачи миграции

Если существующий файл в CP932, безопаснее не переводить его в UTF-8 заодно с обычными правками функций.

Безопасная схема такая.

  • Существующие файлы сохраняют исходные кодировку / BOM / переводы строк
  • Смену кодировки выносят в задачу миграции
  • Массовое преобразование делают только после проверки объектов, зоны влияния и потребителей ниже по цепочке

Многие инциденты с кракозябрами начинаются с доброго «заодно переведём в UTF-8».

7.3 Кодировку рассматривают как часть интерфейса

Для CSV, TXT, журналов, файлов настроек и простых protocol интерфейсом является не только содержимое, но и сама кодировка.

Например, в спецификации стоит зафиксировать как минимум следующее.

  • Этот файл — UTF-8, CP932 или UTF-16LE
  • Если UTF-8, есть ли BOM
  • Перевод строки — LF или CRLF
  • Кто producer, а кто consumer — Linux или Windows
  • Не пересохраняет ли промежуточный batch или ETL

«Передаём как text» спецификацией не является.

7.4 Умолчаниям не доверяют — при записи кодировку указывают явно

И в коде, и в скриптах кодировку безопаснее указывать явно.

Опасны такие ходы мысли.

  • Сохранять с умолчаниями
  • Решить, что «под ОС, наверное, само получится нормально»
  • Считать, что раз в консоли прочиталось, то и с файлом всё в порядке
  • Полагаться на то, что есть auto-detect

Умолчания обычным образом различаются между Windows и Linux, PowerShell 5.1 и 7, редакторами и runtime. Пока кодировка не указана явно, легко оказаться в ситуации, когда всё просто случайно работает.

7.5 Консоль и файл проверяют раздельно

Неброское, но действенное правило.

  • Проверка отображения в консоли
  • Проверка повторным открытием файла

Эти два действия разделяют.

Даже если chcp и отображение терминала совпали, это ничего не даёт, если сохранённый файл в другой кодировке. И наоборот: даже если файл в порядке, при несовпадении кодовой страницы отображения консоли ломается только внешний вид.

7.6 Git кодировку не чинит

Неброско, но важно.

Git по сути только отслеживает байты. То есть сломанные байты он так же добросовестно кладёт в историю.

Поэтому когда:

  • ничего не меняли, а вырос огромный diff
  • загадочный diff только по строкам с японским текстом
  • изменилась только первая строка
  • перевод строки и кодировка изменились вместе

— в таких случаях раньше, чем править содержимое, стоит заподозрить инцидент перекодирования (re-encoding).

Git кодировку не чинитGit по сути только отслеживает байты и кладёт в историю даже сломанные байты, поэтому огромный diff «из ничего» или загадочный diff только по японским строкам стоит сначала считать инцидентом re-encoding, а не правкой содержимого.Git только отслеживает байтыСломанные байты тоже попадают в историю как естьОгромный diff / загадочный diff только по японским строкамРаньше правки содержимого подозревать re-encoding

Рис. 18: Git добросовестно записывает и сломанные байты, поэтому загадочный diff сначала проверяют как re-encoding.

8. Минимальный чек-лист

Чек-лист, который стоит зафиксировать в первую очередь в проектах, где смешаны Windows и Linux.

Поток чек-листаДо правки проверяют текущую кодировку, BOM и перевод строки, во время правки не полагаются на умолчания и auto-detect, после правки снова открывают файл и смотрят diff; массовое преобразование выносят в отдельную задачу миграции.До правки: текущая кодировка, BOM, перевод строкиВо время правки: не оставлять на умолчания и auto-detectПосле правки: снова открыть и посмотреть diffМассовое преобразование вынести в задачу миграции

Рис. 19: Проверку делят на «до / во время / после правки», а массовое преобразование выносят в отдельную задачу.

8.1 До правки

  • Какая сейчас кодировка у этого файла
  • Есть ли BOM
  • Перевод строки — LF или CRLF
  • Записаны ли 2–3 характерные строки с японским текстом
  • Известно ли, кто конечный consumer — Linux или Windows

8.2 Во время правки

  • Нет ли записи, которая зависит от кодировки по умолчанию
  • Не сохраняете ли вы, полагаясь на auto-detect
  • Не используете ли небрежно пути через PowerShell или перенаправление оболочки
  • Не успокаиваетесь ли одним «на экране читается»

8.3 После правки

  • Проверили ли повторным открытием после сохранения
  • Не сломались ли характерные строки ни в Linux, ни в Windows
  • Не прибавилось ли в diff ? или
  • Не сломались ли только первая строка или первый столбец
  • Не стал ли diff большим только из-за BOM / перевода строки

8.4 Что делать как задачу миграции

  • Массовое преобразование CP932 → UTF-8
  • Унификация политики BOM для UTF-8
  • Инвентаризация скриптов, рассчитанных на PowerShell 5.1
  • Документирование путей передачи текста через CI / контейнеры / WSL / SSH
  • Унификация настроек сохранения в редакторах, форматтерах и batch-скриптах

9. Итог

Если описать проблему кодировок Windows одной фразой, суть в том, что мир Unicode и мир устаревших кодовых страниц до сих пор живут рядом.

А инцидентов при связке с Linux становится больше потому, что Linux чаще исходит из UTF-8, и на этом фоне сразу всплывают CP932 и UTF-16 из Windows, кодовая страница консоли и различия версий PowerShell.

Имеет смысл запомнить следующие пять пунктов.

  • Кракозябры — это расхождение в интерпретации байт
  • Сбой отображения и повреждение данных — разные вещи
  • В Windows слои файла, редактора, консоли и API рассматривают раздельно
  • Для текста, которым обмениваются с Linux, UTF-8 — первый вариант
  • Преобразование существующих legacy-файлов отделяют от обычных правок

Если фразу «в Windows появились кракозябры» брать буквально, тема слишком широкая. Но если резать её этими четырьмя вопросами —

  • Что за исходные байты
  • Кто и как записал
  • Кто и как прочитал
  • Уже сохранили?

— картина заметно проясняется.

Кодировка текста — тема неброская, но между Windows и Linux это сам I/O-контракт. Не оставлять его размытым — самая действенная мера.

10. Справочные материалы

Windows / Microsoft

PowerShell / VS Code

GNU / Linux locale

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Почему в Windows появляются кракозябры (mojibake)?
Почти всегда потому, что одну и ту же последовательность байт прочитали как другую кодировку или результат неверного чтения сохранили уже в другой кодировке. Например, байты (E3 81 82), которыми символ «あ» сохранили в UTF-8, в контексте CP932 выглядят как другая строка вроде «縺». Дело не в сложности японского языка: не совпали предположения encode и decode.
Можно ли вернуть файл с кракозябрами к исходному виду?
Если исходные байты не изменились, файл иногда удаётся восстановить, открыв его заново в правильной кодировке. Опасно другое: сохранить уже неверно прочитанное, «сломанное» на экране содержимое. С этой стадии это уже не сбой отображения, а повреждение данных. Если строку Unicode свести к узкой кодовой странице вроде CP932 и символы превратились в «?» или символ-заменитель, однажды потерянные знаки потом не восстановить, даже узнав правильную кодировку.
Если выполнить chcp 65001, файлы тоже станут UTF-8?
Нет. Сменить кодовую страницу консоли и то, какие байты лежат в уже существующем файле, — разные вещи. В Windows кодировка самого файла, интерпретация редактора, входная и выходная кодовые страницы консоли и внутренний формат строк приложения — разные слои, и их нужно рассматривать по отдельности. Суждение «в консоли прочиталось, значит и с файлом всё в порядке» легко оказывается ошибочным, поэтому проверку отображения в консоли и проверку повторным открытием файла стоит разделять.
Какие безопасные правила обмена текстом между Windows и Linux?
Для новых файлов первым вариантом брать UTF-8 и сразу решить, как быть с BOM. Текст, который чаще читают в Linux, по умолчанию делать UTF-8 без BOM; если его читают устаревшие инструменты вроде Windows PowerShell 5.1, наличие или отсутствие BOM явно задавать под получателя. Существующие файлы в CP932 не конвертировать заодно с обычными правками — это отдельная явная задача миграции. У CSV и журналов сама кодировка является интерфейсом, поэтому кодировку, BOM и символ перевода строки стоит записать в спецификацию: так инцидентов меньше.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог