Кодировки текста и переводы строк в Windows — основы искажения текста и CRLF/LF

· Обновлено: · · Windows, Кодировки символов, Искажение кодировки, Перевод строки, UTF-8, CP932, PowerShell, Unicode

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

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

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

Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.

Го Комура (2026). Кодировки текста и переводы строк в Windows — основы искажения текста и CRLF/LF. KomuraSoft LLC. https://comcomponent.com/ru/blog/2026/04/18/000-windows-text-encoding-line-endings/

DOI (зарегистрированный архив)
10.5281/zenodo.21619848
DOI (последняя зарегистрированная версия)
10.5281/zenodo.21619849

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

  • В чём разница между Shift_JIS и UTF-8
  • Почему возникает искажение кодировки
  • В чём разница между CRLF и LF
  • Файл перевели в UTF-8, но почему он всё равно иногда не читается
  • Почему один и тот же файл выглядит по-разному в редакторе, консоли, Excel и Git

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

К тому же в Windows до сих пор сосуществуют мир Unicode и мир кодовых страниц. Добавьте сюда BOM, перевод строки, автоопределение в редакторе, кодовую страницу консоли и преобразование переводов строк в Git — и тема начинает выглядеть запутанной.

В этой статье мы с практической точки зрения разберём часто путаемые в Windows Shift_JIS / UTF-8 / UTF-16, почему возникает искажение кодировки, в чём разница между CRLF и LF и почему эта тема так легко приводит к путанице.

Материал опирается на открытые сведения Microsoft Learn, PowerShell, Git, W3C / Unicode по состоянию на апрель 2026 года. Подробности см. в списке источников в конце статьи.

Для кого эта статья и какие предпосылки

Пункт Содержание
Читатели Разработчики и сотрудники ИТ, которые в Windows обмениваются текстовыми файлами (CSV, журналы, конфигурация, исходный код) с другими отделами, другими системами и стороной Linux
Предварительные знания Особых нет. Статья построена так, чтобы её можно было читать, ещё не различая последовательность байтов и кодировку
Предполагаемая среда Windows 10 / Windows 11. По PowerShell затрагиваются и Windows PowerShell 5.1, и PowerShell 7
Что не разбираем API преобразования кодировки конкретных библиотек, проектирование шрифтов и глифов, нормализацию вроде полной и половинной ширины

Если нужно быстро: для общей картины достаточно глав 1 и 2, для локализации причины — глав 3 и 8, а если нужно зафиксировать правила эксплуатации — начните с главы 7.

Содержание

  1. Что важно понять в первую очередь
  2. Разбираем термины
    • 2.1 Чем отличаются Unicode / UTF-8 / UTF-16 / CP932
    • 2.2 Как относиться к Shift_JIS и CP932
    • 2.3 Ловушки слов ANSI, Unicode и UTF-8N
    • 2.4 Что такое BOM и почему он бывает и у UTF-8
  3. Почему возникает искажение кодировки
    • 3.1 Что на самом деле такое искажение кодировки
    • 3.2 Сбой отображения и повреждение данных — разные вещи
    • 3.3 Сведение к символам, которых нет в целевой кодировке, необратимо
  4. В чём разница между символами перевода строки
    • 4.1 CRLF / LF / CR
    • 4.2 Перевод строки — отдельный вопрос от кодировки
    • 4.3 \n и байты перевода строки в файле не всегда совпадают
  5. Почему в Windows особенно легко запутаться
    • 5.1 Unicode и устаревшие кодовые страницы сосуществуют
    • 5.2 Подписи не согласованы
    • 5.3 Если текст только ASCII, проблема остаётся скрытой
    • 5.4 Содержимое файла, имя файла, консоль и исходный файл — разные уровни
    • 5.5 BOM и перевод строки действуют по отдельной оси
    • 5.6 Инструменты меняют содержимое сами
  6. Типичные сценарии сбоев
  7. Правила, которые снижают число сбоев на практике
  8. Расследование искажения кодировки и разницы в окончаниях строк ведём по этим 5 вопросам
  9. Итог
  10. Похожие статьи
  11. Источники

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

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

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

  • Текстовый файл состоит не из самой строки, а из последовательности байтов + кодировки + перевода строки. Иногда к этому добавляется ещё BOM (Byte Order Mark, метка порядка байтов).
  • Искажение кодировки возникает, когда одну и ту же последовательность байтов декодируют в другой кодировке.
  • Проблемы с переводом строки возникают, когда кодировка верна, но не совпадает предположение о разделителе строк.
  • Unicode и UTF-8 — не одно и то же. Unicode относится к набору символов, а UTF-8 и UTF-16 — это кодировки, которые превращают его в последовательность байтов.
  • То, что в Windows называют «Shift_JIS», на практике безопаснее мыслить как CP932 / японскую кодовую страницу семейства Windows — так разговор меньше расходится.
  • Одной фразы «перешли на UTF-8» для спецификации недостаточно. Правилом эксплуатации это становится только когда решены ещё наличие BOM и тип перевода строки.
  • Источник путаницы — не сам японский язык, а то, что на одной Windows сосуществуют несколько предположений с разной историей.

При работе с текстом в Windows отправная точка — разделить следующие четыре момента.

  1. Какая последовательность байтов в этом файле
  2. В какой кодировке его записали
  3. В какой кодировке его прочитали
  4. Перевод строки — CRLF или LF

Уже одно это разделение заметно снижает вероятность запутаться.

2. Разбираем термины

2.1 Чем отличаются Unicode / UTF-8 / UTF-16 / CP932

Сначала быстрее один раз разобрать слова по отдельности.

Термин Что обозначает Пример Частая путаница
Unicode Рамка, в которой символам сопоставляют номера U+3042 () Считают тем же, что UTF-8
UTF-8 Кодировка, которая переводит Unicode в байты E3 81 82 Считают самим Unicode
UTF-16LE Кодировка, которая переводит Unicode в байты 42 30 Смешивают с пунктом меню Unicode
CP932 Устаревшая японская кодовая страница Windows 82 A0 Считают полностью тем же, что Shift_JIS
CRLF / LF Байты-разделители строк 0D 0A / 0A Считают разновидностью кодировки
BOM (Byte Order Mark) Идентифицирующие байты в начале файла EF BB BF и т. п. Считают названием самой кодировки

Например, даже для одного символа байты различаются в зависимости от кодировки.

Символ: あ

UTF-8    : E3 81 82
CP932    : 82 A0
UTF-16LE : 42 30

Важно понимать: символ и последовательность байтов — разные вещи. На экране приложение работает с тем, что выглядит как «символы», но при сохранении и передаче в конечном счёте обмениваются байтами. Сбои чаще всего происходят именно на границе этого преобразования.

2.2 Как относиться к Shift_JIS и CP932

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

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

Если отнестись к этому небрежно, начинаются вот такие расхождения в разговоре.

  • Сказали «сохрани в Shift_JIS», а собеседник имел в виду CP932 со стороны Windows
  • На стороне Linux / macOS файл обработали как shift_jis, но часть файлов, пришедших из Windows, не воспроизводится корректно
  • Сказали «сохранить в ANSI», но какая это кодовая страница, зависело от окружения

Поэтому в спецификациях и заметках расследования безопаснее по возможности писать так.

  • Не Shift_JIS, а CP932
  • Не ANSI, а ACP (active code page) / в японском окружении обычно CP932
  • Не просто текст, а конкретно, например UTF-8 no BOM, LF

2.3 Ловушки слов ANSI, Unicode и UTF-8N

Вокруг Windows источником путаницы становятся и сами словесные ярлыки.

Особенно сбивают с толку следующие три.

  • ANSI Встречается в интерфейсе Windows и в старых описаниях, но это не ASCII. Чаще всего имеется в виду активная кодовая страница (ACP) данной машины.
  • Unicode В некоторых редакторах и инструментах пункт меню Unicode на деле означает UTF-16LE. Фраза «сохранил в Unicode» не обязательно означает UTF-8.
  • UTF-8N Встречается в редакторах японоязычной среды; обычно это лишь ярлык интерфейса для обозначения UTF-8 без BOM, а не официальное название кодировки.

Иными словами, в Windows одно и то же слово у разных инструментов означает разное. Это первая крупная точка путаницы.

2.4 Что такое BOM и почему он бывает и у UTF-8

BOM — это сокращение от Byte Order Mark (метка порядка байтов): знак U+FEFF, который ставят в начало файла или потока. Как следует из имени, исходная роль — показать порядок байтов.

UTF-16 и UTF-32 представляют символ блоками по 2 / 4 байта, поэтому читающей стороне нужно сообщить, укладывать ли блок «с младшего байта» (little-endian) или «со старшего байта» (big-endian). Для этого в начало ставят BOM, и порядок байтов самого BOM показывает, что дальше действует тот же порядок.

Байты в начале Значение
FF FE UTF-16 little-endian
FE FF UTF-16 big-endian
EF BB BF UTF-8

Здесь возникает вопрос: UTF-8 работает по одному байту и, казалось бы, не имеет проблемы порядка — почему тогда BOM всё равно ставят?

Причина в том, что BOM у UTF-8 используют не как метку порядка байтов, а как подпись (сигнатуру) со смыслом «этот файл — UTF-8». UTF-8 совместим с ASCII, поэтому по содержимому из одних латинских букв и цифр его не отличить от устаревшей кодовой страницы. Если в начале лежит EF BB BF, читающая сторона может решить: «читать не как активную кодовую страницу, а как UTF-8».

Обратная сторона выглядит так.

  • Плюс BOM: читающей стороне труднее ошибиться в угадывании кодировки. В частности, Windows PowerShell 5.1 читает файлы скриптов без BOM как активную кодовую страницу, поэтому скрипты с символами вне ASCII без BOM ломаются.
  • Минус BOM: в начале появляются лишние 3 байта. Обработчики, которые не знают про BOM, воспринимают это так, будто в начало первой строки попал невидимый символ. Отсюда сбои вроде «имя только в первом столбце заголовка CSV не совпадает» или «строка #! в shell-скрипте не распознаётся».

То есть BOM у UTF-8 — не вопрос «правильно / неправильно его ставить», а настройка, которую выбирают в зависимости от того, кто будет читать файл. Именно поэтому, как в 7.1, недостаточно «перейти на UTF-8»: нужно ещё решить, with BOM это или no BOM.

3. Почему возникает искажение кодировки

3.1 Что на самом деле такое искажение кодировки

Суть искажения кодировки довольно проста.

  1. Строку превращают в последовательность байтов в некоторой кодировке
  2. Эти байты превращают обратно в строку в другой кодировке
  3. Если предположения не совпадают, получается другая строка

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

E3 81 82

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

Если сформулировать искажение кодировки одной строкой, получится так.

Одну и ту же последовательность байтов прочитали как другую кодировку.

кодируем в UTF-8 и сохраняемдекодируем как UTF-8декодируем как CP932Символ あБайты E3 81 82в файле лежит только этоСимвол あ ── предположения совпалиДругая строка вроде 縺 ── предположения разошлись

Рис. 1: На развилке содержимое файла (последовательность байтов) одно и то же. Различается только предположение, которое поставила читающая сторона

3.2 Сбой отображения и повреждение данных — разные вещи

Здесь важно разделять стадию, когда ещё можно вернуть исходное, и стадию, когда вернуть уже трудно.

Например, при следующей последовательности восстановление ещё возможно.

  1. Файл в UTF-8 открывают как CP932
  2. На экране это выглядит как 縺�
  3. Файл ещё не сохраняли

На этой стадии исходные байты по-прежнему остаются UTF-8. Если заново открыть файл в правильной кодировке, содержимое часто возвращается.

Опасна следующая последовательность.

  1. Файл в UTF-8 неверно читают как CP932
  2. Содержимое, которое выглядит сломанным, сохраняют как есть
  3. Исходные байты UTF-8 теряются

На этом этапе речь уже идёт не о сбое отображения, а о повреждении данных.

открываем в предположении CP932открываем зановосохраняем как естьФайл, сохранённый в UTF-8байты — E3 81 82На экране видно что-то вроде 縺= сбой отображения. Байты ещё UTF-8Если указать правильную кодировку, содержимое возвращаетсяНеверно прочитанную строку записывают обратно в CP932= повреждение данных. Исходные байты утраченыДаже узнав позже правильную кодировку, вернуть уже нельзя

Рис. 2: Развилка — ровно один шаг: «открыли заново или сохранили». В расследовании это нужно проверять первым

На практике важно не сводить всё к одной фразе «файл искажён», а как минимум разделять следующие два вопроса.

  • Остаются ли сами байты корректными
  • Было ли уже пересохранено неверно прочитанное содержимое

3.3 Сведение к символам, которых нет в целевой кодировке, необратимо

Ещё одна опасная ситуация — сведение строки Unicode к узкой кодовой странице вроде CP932.

Если в строке есть символы, которых нет у принимающей стороны, происходит одно из следующего.

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

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

Однажды утраченную информацию не восстановить, даже узнав впоследствии правильную кодировку.

4. В чём разница между символами перевода строки

4.1 CRLF / LF / CR

Перевод строки — это тоже байты.

  • CR = carriage return = 0D
  • LF = line feed = 0A
  • В текстовых файлах Windows традиционно используется CRLF (0D 0A)
  • В Linux / Unix обычно используется LF (0A)
  • Одиночный CR иногда встречается в старых контекстах вроде классических Mac

В виде таблицы это выглядит так.

Перевод строки Байты Основной контекст
CRLF 0D 0A Традиционные текстовые файлы Windows, устаревшие инструменты
LF 0A Linux / macOS / большинство инструментов разработки
CR 0D Довольно старые устаревшие данные

4.2 Перевод строки — отдельный вопрос от кодировки

Это довольно важный момент.

Перевод строки — отдельный вопрос от кодировки.

Даже в одном и том же файле UTF-8 перевод строки может быть и CRLF, и LF. Например, для содержимого «A, перевод строки, B» байты будут различаться так.

UTF-8 + LF   : 41 0A 42
UTF-8 + CRLF : 41 0D 0A 42

То есть вполне обычная ситуация, когда:

  • UTF-8, но отличается только перевод строки
  • CP932, но перевод строки — LF
  • UTF-16LE, но перевод строки — CRLF

Поэтому когда говорят «перешли на UTF-8, а всё равно не то», на деле иногда расходится не кодировка, а только перевод строки.

4.3 \n и байты перевода строки в файле не всегда совпадают

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

То, что в исходном коде написано \n, ещё не значит, что в файле обязательно окажется только 0A. В текстовом режиме языка, среды выполнения или I/O API символ \n в Windows иногда преобразуется в CRLF.

То есть может разойтись следующее.

  • Представление перевода строки в исходном коде
  • Строка во время выполнения
  • Байты, сохранённые в файле
  • Перевод строки, видимый в редакторе

Из-за этого случаются сбои вроде «я был уверен, что написал LF, но в файле оказался CRLF».

Современные редакторы спокойно работают с одним лишь LF, но у сопутствующих инструментов, устаревших приложений и рабочих процессов предположение о CRLF всё ещё сохраняется. Поэтому проблема перевода строки — не «дела давно минувших дней»: она и сегодня регулярно встречается на практике.

5. Почему в Windows особенно легко запутаться

5.1 Unicode и устаревшие кодовые страницы сосуществуют

Это главная причина, по которой в Windows всё так запутано.

В Windows сохраняются оба пути:

  • обработка через Unicode
  • обработка на основе кодовых страниц

Более новые приложения, веб и кроссплатформенные ресурсы тяготеют к UTF-8, тогда как в старых CSV, TXT, журналах, вокруг Excel и в интеграции рабочих систем сохраняется CP932. Кроме того, в части вывода и вокруг некоторых API вполне обычным делом остаётся и UTF-16LE.

Иными словами, на одной машине с Windows сосуществует сразу несколько текстовых культур.

5.2 Подписи не согласованы

Путаницу усиливает не столько сама технология, сколько расхождение ярлыков.

  • Говорят Shift_JIS, а по факту это CP932
  • Говорят ANSI, а по факту это активная кодовая страница
  • Говорят Unicode, а по факту это UTF-16LE
  • Говорят UTF-8, но наличие или отсутствие BOM не определено
  • Появляются собственные ярлыки редакторов вроде UTF-8N

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

5.3 Если текст только ASCII, проблема остаётся скрытой

Это тоже значимый фактор.

Поскольку UTF-8 совместим с диапазоном ASCII, файл, содержащий только латинские буквы, цифры и знаки, иногда «более-менее читается» даже при неверном предположении о кодировке. На стороне CP932 диапазон, эквивалентный ASCII, тоже редко выглядит искажённым внешне, поэтому проблема не всплывает на поверхность.

В результате складывается такая картина.

  • Конфигурационный файл только на английском выглядит без проблем
  • Стоит добавить одну строку японского текста, как всё ломается
  • Проблема, скрывавшаяся всё это время, впервые проявляется уже в эксплуатации

Поэтому сбои кодировки часто выглядят так, будто «вчера всё работало, а сегодня вдруг сломалось». На деле чаще всего оказывается, что мина была заложена давно, а проявилась она лишь в момент появления символов вне ASCII.

5.4 Содержимое файла, имя файла, консоль и исходный файл — разные уровни

В Windows легко запутаться, если объединить всё нижеперечисленное одним словом «кодировка».

  • Имя файла / путь
  • Содержимое файла
  • Отображение в консоли
  • Кодировка самого файла исходного кода
  • Формат строк во время выполнения
  • Отображение в буфере обмена и элементах GUI

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

Такие операции, как chcp 65001, в основе своей влияют лишь на предположение на стороне консоли и не меняют байты уже существующего файла.

Более того, даже если файл исходного кода в UTF-8, журнал, записываемый во время выполнения, не обязательно окажется тоже в UTF-8. Каждый раз нужно чётко разделять, о кодировке какого именно уровня идёт речь.

Кстати, в японской Windows символ \ иногда отображается как знак иены, и это тоже легко путают с вопросом кодировки. Однако в большинстве случаев это проблема отображающего шрифта или глифа, а не изменение смысла разделителя пути или экранирующего символа.

5.5 BOM и перевод строки действуют по отдельной оси

Одного лишь слова UTF-8 достаточно только для половины дела.

На практике важны и следующие моменты.

  • Есть BOM или нет
  • CRLF или LF используется для перевода строки

Например, даже в рамках одного и того же UTF-8:

  • есть инструменты Windows, которые читают файл только при наличии BOM
  • есть обработка на стороне Unix, где из-за BOM в первом столбце появляются лишние символы
  • есть устаревшие инструменты, которым трудно работать с одиночным LF
  • при CRLF могут портиться shell-скрипты и diff

То есть сбой возможен, даже если кодировка совпадает.

5.6 Инструменты меняют содержимое сами

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

  • Редактор автоматически определяет кодировку
  • При сохранении добавляет или убирает BOM
  • Git преобразует CRLF / LF
  • Оболочка или команда сохраняет файл в кодировке по умолчанию
  • Экспорт CSV использует неожиданную кодовую страницу
  • В разных версиях PowerShell и других инструментов различаются значения по умолчанию

То есть даже если исполнитель ничего явно не указывал, на практике в Windows какой-то из слоёв сам добавляет своё предположение.

Это и есть истинная причина ситуации «я ничего не менял, а всё сломалось». На деле нередко меняет не человек, а значения по умолчанию инструмента.

6. Типичные сценарии сбоев

Типичные сбои в виде таблицы выглядят так.

Ситуация Что на самом деле расходится Типичный симптом
Устаревший инструмент Windows принимает конфигурационный файл UTF-8 no BOM за ANSI / CP932 Предположение о декодировании Искажается только японский текст
CSV в CP932 передают в систему, работающую по предположению UTF-8 Предположение о декодировании , ошибка декодирования, бессмысленный японский текст
Журнал в UTF-16LE передают в текстовый инструмент Unix Предположение о кодировке Примешиваются NUL-байты, файл выглядит как двоичный
Файл исходного кода в LF в другом окружении преобразуют в CRLF Предположение о переводе строки Огромный diff по концам строк, сбои в скриптах
Неверно прочитанное содержимое сохраняют как есть Сами байты превращаются в нечто иное Необратимое повреждение данных
В спецификации написано только «выгрузить CSV» Интерфейс не определён Excel читает, а другой инструмент ломается
Решили лишь «унифицировать на UTF-8» BOM / перевод строки не определены Не срабатывают только отдельные инструменты

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

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

Далее речь пойдёт о том, какие правила эксплуатации стоит определить, чтобы снизить число сбоев.

7.1 Определяем базовый вариант для новых файлов

Для новых файлов разумно в первую очередь выбирать UTF-8. Но одного этого недостаточно.

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

  • UTF-8 with BOM или UTF-8 no BOM
  • CRLF или LF для перевода строки
  • Кто будет читать этот файл
  • Нужна ли совместимость с устаревшими инструментами Windows
  • Будут ли читать файл также Linux / macOS / CI / контейнеры

Например, для исходного кода и конфигурации, рассчитанных на несколько платформ, первым кандидатом обычно становится UTF-8 no BOM + LF. С другой стороны, если нужно подстроиться под старые инструменты Windows или существующую эксплуатацию, иногда по-прежнему нужны UTF-8 with BOM или CP932 + CRLF.

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

7.2 Существующие устаревшие файлы не меняем по своей инициативе

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

Безопасная схема эксплуатации выглядит так.

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

Сбои с искажением кодировки нередко начинаются с благих намерений — «заодно модернизировать».

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

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

В спецификации безопаснее указать как минимум следующее.

  • Кодировка
  • Наличие BOM
  • Тип перевода строки
  • Наличие заголовка
  • Правила кавычек / разделителя
  • Каким инструментом проводилась проверка

Например, одних лишь трёх букв CSV недостаточно. Только когда написано UTF-8 with BOM, CRLF, разделитель — запятая, с заголовком, разговор перестаёт расходиться.

7.4 На границах чтения и записи указываем явно

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

  • При чтении и записи файла явно указывать кодировку
  • Учитывать кодировку и при передаче текста между процессами
  • В процедурах экспорта / импорта фиксировать в спецификации и перевод строки
  • Не делать небрежное перенаправление оболочки производственным каналом передачи данных

Особенно в Windows «удалось сохранить» и «сохранилось с правильными байтами» — не одно и то же.

7.5 Делимся правилами Git и редактора

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

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

  • Использовать ли LF как основной вариант для исходного кода
  • Допускать ли CRLF для текста, предназначенного только для Windows
  • Как зафиксировать это в .gitattributes
  • Как делиться настройками редактора

Важно рассматривать кодировку и перевод строки отдельно друг от друга. Даже если Git приводит переводы строк к единому виду, сбои с кодировкой никуда не денутся.

7.6 Не останавливаемся на «файл искажён» — говорим, что именно разошлось

На практике хорошо помогает такая замена формулировок.

  • Плохая формулировка: «файл искажён»
  • Хорошая формулировка: «похоже, файл UTF-8 no BOM открывают с предположением CP932»
  • Плохая формулировка: «с концами строк что-то не так»
  • Хорошая формулировка: «файл в LF преобразуется в CRLF, из-за чего растёт diff»

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

7.7 Как это проверить — на что смотреть в каждом инструменте

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

Инструмент Где смотреть текущую кодировку Где менять
VS Code Строка состояния в правом нижнем углу окна. Рядом стоят кодировка вроде UTF-8 и обозначение перевода строки CRLF / LF Щелчок по кодировке в строке состояния открывает варианты открыть заново и сохранить. Значение по умолчанию — в настройках files.encoding
Блокнот В строке состояния отображается кодировка документа «Файл» → «Сохранить как», поле «Кодировка» в диалоге
PowerShell Надёжнее всего смотреть первые байты файла напрямую (команда ниже) Параметр -Encoding у записывающего командлета. Значение по умолчанию задаётся через $PSDefaultParameterValues

VS Code

Кодировка VS Code по умолчанию — UTF-8 (без BOM). Чтобы сменить её для конкретного файла, щёлкните по отображению в строке состояния и выберите: открыть заново уже открытый файл в другой кодировке или сохранить заново в другой кодировке. Если содержимое только неверно прочитали, сначала открывайте заново — это соответствует решению из 3.2.

Чтобы сменить значение по умолчанию, задайте в настройках files.encoding. Значения — utf8 (без BOM), utf8bom (с BOM), utf16le, windows1252 и другие. Можно задавать и отдельно по языкам.

{
  "files.encoding": "utf8",
  "[powershell]": {
    "files.encoding": "utf8bom"
  }
}

Разделение из 2.4 — ставить BOM только у скриптов, которые будут работать в Windows PowerShell 5.1, — как раз выражается этой настройкой по языкам.

Блокнот

Начиная с Windows 10 Build 18963 у Блокнота в строке состояния есть столбец с кодировкой документа, а значение по умолчанию для нового файла — UTF-8 (без BOM). При сохранении откройте «Файл» → «Сохранить как» и выберите значение в поле «Кодировка» диалога. В этом поле можно выбрать и наличие BOM, поэтому здесь отражают политику, зафиксированную в 7.1.

Когда сообщают «открыл в Блокноте — текст исказился», быстрее всего сначала проверить это поле и отображение в строке состояния.

PowerShell

Значения по умолчанию в PowerShell сильно зависят от версии. Не зная этого, часто приходят с жалобой «вывел из PowerShell — инструмент не читает».

  • PowerShell 6 и новее (линейка PowerShell 7): значение по умолчанию для любого текстового вывода — utf8NoBOM, то есть UTF-8 без BOM.
  • Windows PowerShell 5.1: значения по умолчанию не согласованы между командлетами.

Основные случаи в 5.1 в виде таблицы.

Команда Значение по умолчанию при записи
Out-File, операторы перенаправления > >> UTF-16LE
Set-Content, Add-Content (когда назначение пустое или не существует) Default (активная кодовая страница; в японском окружении CP932)
Export-Csv Ascii
Start-Transcript UTF-8 with BOM
New-Item -Type File -Value UTF-8 no BOM

Значения по умолчанию есть и на стороне чтения. Если BOM нет, Get-Content и движок PowerShell при чтении файлов скриптов считают кодировку Default (активная кодовая страница), а Import-Csv и Select-String — UTF-8. Даже для одного и того же файла предположение меняется в зависимости от того, каким командлетом его читают.

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

# PowerShell 7: показать первые 3 байта в шестнадцатеричном виде
Get-Content -Path .\sample.txt -AsByteStream -TotalCount 3 | Format-Hex

# В Windows PowerShell 5.1 нет -AsByteStream, поэтому используют -Encoding Byte
Get-Content -Path .\sample.txt -Encoding Byte -TotalCount 3 | Format-Hex

Если в начале EF BB BF — это UTF-8 with BOM, если FF FE — UTF-16LE, иначе BOM нет. Это тот же взгляд, что и в таблице из 2.4.

Чтобы явно зафиксировать значение по умолчанию, используют $PSDefaultParameterValues.

# Задать UTF-8 по умолчанию для всех командлетов с параметром Encoding
$PSDefaultParameterValues['*:Encoding'] = 'utf8'

Здесь есть ловушка между версиями. Одна и та же строка utf8 в Windows PowerShell 5.1 означает UTF-8 with BOM, а начиная с PowerShell 6 — UTF-8 no BOM. Если нужно зафиксировать и наличие BOM, безопаснее явно указать доступные с PowerShell 6 значения utf8BOM / utf8NoBOM.

Кроме того, $OutputEncoding — это кодировка для обмена с внешними программами, и она не влияет на кодировку, с которой перенаправление или командлет сохраняет в файл. Это место легко перепутать.

8. Расследование искажения кодировки и разницы в окончаниях строк ведём по этим 5 вопросам

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

  1. Какая последовательность байтов сейчас в этом файле
    • Это UTF-8?
    • UTF-8 with BOM?
    • CP932?
    • UTF-16LE?
  2. Кто и с каким предположением записал файл первым
    • Редактор
    • Устаревшее приложение
    • Экспорт из Excel
    • Оболочка / скрипт
    • Пакетный процесс / промежуточное ПО
  3. Кто и с каким предположением читает файл сейчас
    • Автоопределение редактора
    • Кодовая страница консоли
    • Кодировка по умолчанию в библиотеке
    • Спецификация на стороне импорта
  4. Что представляют собой BOM и перевод строки
    • BOM есть или нет
    • CRLF / LF
  5. Было ли уже сохранено неверно прочитанное содержимое
    • Это пока только отображение?
    • Или файл уже пересохранён и байты утрачены?

Когда ответы на эти пять вопросов найдены, причина, как правило, становится видна.

9. Итог

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

Особенно стоит запомнить следующие шесть пунктов.

  • Искажение кодировки — результат чтения одних и тех же байтов в другой кодировке
  • Проблема перевода строки лежит в плоскости, отдельной от кодировки
  • Не стоит слишком доверять словам Shift_JIS, CP932, ANSI, Unicode буквально
  • Одной фразы «перешли на UTF-8» недостаточно, нужны ещё BOM и тип перевода строки
  • Сбой отображения и уже сохранённое повреждение данных нужно рассматривать раздельно
  • В спецификации писать не просто текст, а конкретно, например UTF-8 no BOM, LF

Иными словами, при работе с текстом в Windows практичнее считать это не «вопросом строк», а вопросом того, как согласовать договорённость о байтах.

10. Похожие статьи

11. Источники

  1. Microsoft Learn, Code Page Identifiers - Win32 apps https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
  2. Microsoft Learn, about_Character_Encoding - PowerShell https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-7.6
  3. Microsoft Learn, Understanding file encoding in VS Code and PowerShell https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding?view=powershell-7.6
  4. W3C Internationalization, Character encodings: Essential concepts https://www.w3.org/International/articles/definitions-characters/
  5. Git documentation, gitattributes https://git-scm.com/docs/gitattributes
  6. Git documentation, git-config https://git-scm.com/docs/git-config
  7. Microsoft Learn, The Unicode standard - Globalization (байты BOM и то, что BOM у UTF-8 используют как сигнатуру) https://learn.microsoft.com/en-us/globalization/encoding/unicode-standard
  8. Microsoft Learn, What was new in 20H1 Windows 10 Insider Preview Builds (перевод Блокнота на UTF-8 по умолчанию в Build 18963 и появление отображения кодировки в строке состояния) https://learn.microsoft.com/en-us/previous-versions/windows-insider/archive/new-in-20h1

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

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

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

Технические консультации и ревью дизайна

В проектах, где предположения о кодировке CSV, журналов и конфигурационных файлов расходятся между Windows и Linux, заранее зафиксировать контракт ввода-вывода и правила эксплуатации обычно помогает снизить число инцидентов.

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

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

Почему возникает искажение кодировки?
Потому что одну и ту же последовательность байтов прочитали в кодировке, отличной от той, в которой её записали. Например, символ «あ», сохранённый в UTF-8, даёт байты E3 81 82, но при чтении в предположении CP932 выглядит как другая строка вроде «縺». Повреждён не японский текст, а предположение о декодировании. Если же неверно прочитанное содержимое сохранить как есть, исходные байты будут утрачены: это уже не сбой отображения, а повреждение данных. Поэтому важно заново открыть файл в правильной кодировке до сохранения.
В чём разница между CRLF и LF?
Это разница в байтах, которыми обозначают границу строки. CRLF — два байта 0D 0A, традиционно используемые в текстовых файлах Windows; LF — один байт 0A, обычный в Linux / macOS и во многих инструментах разработки. Важно, что перевод строки — отдельный вопрос от кодировки. В одном и том же файле UTF-8 перевод строки может быть и CRLF, и LF, поэтому фраза «перешли на UTF-8, а всё равно не то» иногда означает, что разошлась не кодировка, а только тип перевода строки.
Shift_JIS и CP932 — это одно и то же?
В разговоре этого обычно достаточно, но на практике безопаснее их различать. Если нужно точно мыслить об устаревшем японском тексте Windows, лучше думать о CP932 или о японской кодовой странице семейства Windows. Точно так же «ANSI» в Windows чаще всего означает активную кодовую страницу данной машины, а пункт меню «Unicode» в редакторе иногда означает UTF-16LE. В спецификациях и заметках расследования стоит писать конкретно, например «UTF-8 no BOM, LF», чтобы разговор меньше расходился.
Что нужно зафиксировать в спецификации текстового файла?
Одной фразы «перешли на UTF-8» недостаточно: правилом эксплуатации это становится только когда решены ещё наличие BOM и тип перевода строки. Для файлов обмена вроде CSV и журналов безопаснее прописать кодировку, наличие BOM, перевод строки, наличие заголовка и правила разделителя. Для исходного кода и конфигурации, рассчитанных на несколько платформ, первым кандидатом обычно становится UTF-8 no BOM + LF, а если нужно подстроиться под старые инструменты Windows, иногда по-прежнему нужны UTF-8 with BOM или CP932 + CRLF.

Об авторе

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

Го Комура

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

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

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

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