Переносы строк без скрытых преобразований
LF, CRLF и последний перевод строки влияют на байты и границы записей.
Разные уровни структуры
Перенос строки может быть разделителем записей, частью текстового значения или просто оформлением документа. В JSONL он отделяет JSON-значения, а в pretty JSON обычно разделяет элементы оформления. В CSV перенос внутри кавычек является частью поля. Прежде чем менять окончания строк, нужно понять, на каком уровне действует операция.
LF — один байт 0A в UTF-8, CRLF — последовательность 0D 0A. Одиночный CR тоже встречается в старых данных и специальных протоколах. Замена пар CRLF на LF отличается от удаления всех CR. Последняя операция может испортить содержимое, если CR использовался отдельно или оказался частью бинарного файла.
Текстовый режим
Некоторые среды при чтении текста автоматически превращают разные окончания в один символ новой строки. При записи может произойти обратное преобразование по правилам платформы. Это удобно для редактора, но мешает побайтовому сравнению и точному копированию. Если нужно сохранить представление, читайте байты либо явно задавайте поведение newline.
Для Python csv рекомендуют открывать текстовый файл с newline="". Тогда модуль получает возможность самостоятельно обрабатывать границы записей и quoted переносы. Обычное построчное split не заменяет csv.reader. Наличие одинакового количества видимых строк в редакторе не доказывает, что таблица сохранила то же количество записей.
Последняя строка
Файл может заканчиваться LF или непосредственно последним символом значения. Для обычного текста оба варианта часто читаются, но при объединении файлов разница становится существенной. Если первый JSONL не имеет завершающего LF, простой cat соединит последнее значение первого файла с первым значением второго. Полученная строка уже не является одним JSON.
Для собственного JSONL разумно всегда записывать LF после каждой записи, включая последнюю. При чтении отдельно определите политику пустых строк. Не следует молча считать пустую строку null: это разные данные. Ошибка должна указывать номер физической строки и источник, чтобы восстановление не требовало повторного просмотра всего набора.
Что увидит инструмент сравнения
Некоторые программы сравнения умеют игнорировать CR на конце строки. Такой режим полезен для просмотра смысловых изменений, но не должен подменять проверку публикуемых байтов. Сначала посмотрите обычный diff, затем при необходимости включите согласованное игнорирование. Сохраните отдельный хеш каждого представления. В репозитории настройка окончания строк тоже является входом процесса: checkout может менять рабочие байты в зависимости от правил проекта. Для скачиваемых календарей и тестовых файлов проверяйте read_bytes, а не результат текстового чтения. Иначе сама проверка успеет скрыть ошибку нормализации. Редакторная подсветка последней строки не даёт надёжного ответа, есть ли завершающий LF; это нужно проверить по реальному содержимому файла.
Проверка и ограничения
Перед нормализацией подсчитайте CRLF, одиночные CR и LF. Для смешанного файла сохраните оригинал и выясните происхождение: смесь может быть результатом склейки разных источников. После преобразования сравните декодированные значения по структуре формата, а не только размер. Размер уменьшится на количество заменённых CR, но это лишь вспомогательная проверка.
Некоторые форматы требуют конкретных переносов. iCalendar и vCard используют CRLF и правила складывания длинных строк. Универсальный редактор, сохраняющий всё как LF, может нарушить ожидаемый обмен. Folding тоже не равен простому переносу по ширине окна: продолжение свойства имеет собственный синтаксис. Проверяйте требования конкретной версии формата.
Изменение переносов меняет SHA-256 и делает старую побайтовую подпись неприменимой. Храните нормализованную версию как новый артефакт с новым хешем. Если обратимость важна, одного флага «был Windows» недостаточно для файла со смешанными окончаниями. Нужен оригинал или точная карта замен. Простая операция остаётся безопасной, когда её область действия ограничена и исходные байты доступны для сравнения.