Перенос строки — это управляющий символ или пара символов, которые отмечают конец одной строки текста и начало следующей. На практике встречаются три разных представления — LF, CRLF и CR, и они не взаимозаменяемы на уровне байтов.
Содержание
Ниже — чем они отличаются, почему путаница между ними ломает git-диффы и текстовые файлы, и как перенос строки ведет себя в языках программирования.
Три формата переноса строки
Исторически оба символа пришли из телетайпов: CR (carriage return, возврат каретки) возвращал печатающую головку к началу строки, а LF (line feed, подача строки) сдвигал бумагу на строку вниз без возврата к началу.
Это упрощенная историческая модель — в компьютерах эти операции давно не связаны с физическим движением, но названия и коды символов остались от нее.
| Формат | Символы | Код | Где принят |
|---|---|---|---|
| LF | \n |
0x0A | Unix, Linux, macOS после перехода на Mac OS X (2001) |
| CRLF | \r\n |
0x0D 0x0A | Windows; текстовый режим ряда сетевых протоколов |
| CR | \r |
0x0D | Классическая Mac OS, до перехода на Mac OS X |
CR как отдельный формат переноса строки сегодня практически не встречается — это факт про историю платформы, а не про то, что символ \r вышел из употребления вообще: он используется, например, для перерисовки строки на месте (прогресс-бар в консоли).
Почему различие важно на практике
Git сравнивает файлы побайтово. Если один разработчик сохранил файл с LF, а другой — с CRLF, git увидит «измененными» все строки файла, хотя текст не менялся по смыслу. Диффы становятся нечитаемыми, а слияние веток — конфликтным на пустом месте.
Решение — нормализация: настройка core.autocrlf на стороне git и файл .gitattributes с правилом * text=auto, который приводит перенос строки в репозитории к единому формату (обычно LF) независимо от ОС разработчика.
Похожая проблема встречается при открытии Windows-файла в редакторе, который ждет только LF: символ \r остается «повисшим» в конце каждой строки и виден как посторонний знак вместо перевода строки.
В сетевых текстовых протоколах (HTTP, SMTP) спецификации исторически требуют CRLF как разделитель строк заголовков. Многие серверы и клиенты дополнительно принимают одиночный LF ради совместимости, но в собственном коде правильнее явно формировать именно CRLF, не полагаясь на снисходительность конкретной реализации.
Перенос строки в языках программирования
Здесь важно различать два уровня: что хранится внутри строки в памяти программы и что записывается на диск или уходит в сеть. Их легко перепутать, потому что для программиста оба уровня выглядят как «перенос строки».
В Python внутри программы перенос строки всегда хранится как один символ \n — это гарантия языка, не зависящая от ОС. При чтении файлов в текстовом режиме питон делает трансляцию: любые из \r\n, \r, \n превращаются в один \n (режим называется universal newlines).
При записи \n заменяется на os.linesep — значение, зависящее от платформы, на которой запущен интерпретатор.
В Java аналогичную роль платформенной переменной играет System.lineSeparator() — если в коде явно написать "\n", трансляции не будет и в файл попадет ровно LF на любой ОС.
В JavaScript автоматической трансляции нет вообще: "\n" в строке остается одним байтом 0x0A независимо от системы, а нужный формат при записи файла (например, через Node.js fs) программист выбирает сам.
Как проверить, что реально в файле
Полагаться на предположение «наверное, LF» опасно — платформозависимое поведение нужно смотреть, а не угадывать. Ниже — воспроизводимый способ через repr() и os.linesep, а не через фиксированный hex-дамп, который отличался бы на Windows и Unix.
import os
# внутри python перенос строки - всегда один символ \n,
# это гарантия языка, а не платформы
text = "line1\nline2"
print(repr(text))
# 'line1\nline2'
# os.linesep - то, что ОС использует "снаружи", для текстовых файлов;
# значение платформозависимое, поэтому смотрим через repr(), а не пишем руками
print(repr(os.linesep))
# Linux/macOS: '\n'
# Windows: '\r\n'
# запись в текстовом режиме без newline='' транслирует \n в os.linesep
with open("demo.txt", "w") as f:
f.write("line1\nline2\n")
with open("demo.txt", "rb") as f:
print(repr(f.read()))
# Linux/macOS: b'line1\nline2\n'
# Windows: b'line1\r\nline2\r\n'
# newline='' отключает трансляцию - на диске окажется ровно то, что написано
with open("demo_lf.txt", "w", newline="") as f:
f.write("line1\nline2\n")
with open("demo_lf.txt", "rb") as f:
print(repr(f.read()))
# на любой ОС: b'line1\nline2\n'
Типичная ошибка — резать текст жестко по "\r\n", предполагая один формат:
lines = data.split("\r\n")
На LF-файле результат — список из одного элемента: искомой подпоследовательности \r\n в байтах нет, деления не происходит вообще. Исправление — использовать splitlines(), который одинаково распознает LF, CRLF и CR:
lines = data.decode().splitlines()
Выводы
- LF, CRLF и CR — три разных байтовых представления переноса строки, не взаимозаменяемых напрямую.
- Unix и современный macOS используют LF (
\n), Windows — CRLF (\r\n); CR отдельно как формат переноса строки остался в истории классической Mac OS. - Смешение форматов в одном git-репозитории делает диффы нечитаемыми — решается нормализацией через
.gitattributesиcore.autocrlf. - В Python внутри программы перенос строки — всегда
\n; конкретные байты на диске зависят от режима открытия файла иos.linesep. - Реальный байтовый состав файла нужно проверять через
repr()/бинарное чтение, а не предполагать «по умолчанию».
Где применяется / связь с практикой
Корректная работа с текстовыми файлами на разных платформах — базовый навык при обработке логов, парсинге CSV, настройке git и написании кода, который потом запускают и на Linux-сервере, и на Windows-машине коллеги.
Освойте тему на практике
Разбираться в этом стоит не по отдельным статьям, а системно — с этого и других базовых механизмов Python начинается курс Python для начинающих в Otus.
Освойте тему на практике
Если пока не уверены, подходит ли формат курса именно вам, можно начать с открытых уроков — бесплатный формат для знакомства с подачей перед покупкой курса.
FAQ
Почему при открытии Windows-файла в Unix-редакторе видны лишние символы ^M?
Это символ CR (\r), оставшийся в конце каждой строки CRLF-файла. Редактор, который ожидает только LF, показывает \r как отдельный видимый знак вместо перевода строки.
Можно ли встретить LF и CRLF одновременно в одном файле?
Да, это называется mixed line endings — обычно результат правки файла в разных ОС или редакторах без нормализации. Git и большинство линтеров помечают такие файлы предупреждением; правильнее привести весь файл к одному формату.
Как перенос строки представлен внутри JSON?
Внутри строкового значения JSON перенос кодируется экранированной последовательностью \n (или \r\n, если нужны оба байта). Сам JSON-документ как файл на диске может использовать любой формат переноса строк между токенами — на разбор значений парсером это не влияет.



