CRLF против LF ОШИБКА ЗАКРЫТИЯ СТРОКИ, КОТОРАЯ ПРОБЛЕМАМИ В ПРОЦЕССЕ ИНТЕГРАЦИИ
Ваш скрипт работает локально, но не работает в CI с ошибкой интерпретатора, которая неясна. Причина обычно заключается в концах строк CRLF. Изучите, что такое CRLF, почему они нарушают работу линейных потоков в Linux, и как решить эту проблему навсегда с помощью .gitattributes и настроек редактора.
Ваш скрипт Shell работал корректно на ноутбуке. Вы отправляете его в GitHub, CI его обнаруживает, и пайплайн завершается с криптичной ошибкой — что-то вроде /bin/bash^M: bad interpreter или задача, которая завершается кодом 126 без очевидной причины. Скрипт синтаксически корректен. Вы не изменили ни одной строки. Но сборка сломана.
В девятерых случаях из десяти виновником является конец строки. Конкретно, Windows-стиль CRLF, скрытый внутри файла, который Linux ожидает найти только в виде LF. В этой статье объясняется, что эти два символа на самом деле означают, почему различие имеет значение для большинства разработчиков и как устранить проблему навсегда.
Что означают CRLF и LF
Каждый текстовый файл должен иметь способ отметить конец строки. В разных операционных системах используются два контрольных символа:
- LF (Line Feed,
\n, шестнадцатеричный код0x0A) — стандарт Unix и Linux. macOS использует его с OS X. Один байт на конец строки. - CRLF (Carriage Return + Line Feed,
\r\n, шестнадцатеричный код0x0D 0x0A) — стандарт Windows, наследованный от DOS, который, в свою очередь, наследовал его от телетайпов. Два байта на конец строки.
Названия происходят из физических действий старого типографа: возврат каретки перемещал печатающую головку в начало строки; возврат листа поднимал бумагу на одну строку. DOS кодировал оба действия буквально. Unix выбрал минимальный подход и сохранил только возврат строки.
Почему CI ломается, а на ноутбуке всё работает
Если вы пишете код на Windows и ваш редактор сохраняет файлы с CRLF, всё работает локально — инструменты Windows обрабатывают оба формата прозрачно. Но ваш пайплайн CI, скорее всего, работает на Linux, и интерпретаторы оболочек Linux рассматривают \r как печатаемый символ, а не как пробел. Когда bash читает строку скрипта, заканчивающуюся на \r\n, он видит команду вместе с завершающим возвратом каретки. Результат выглядит так:
/bin/bash^M: bad interpreter: No such file or directory
The ^M в этой ошибке — это то, как терминал отображает \r. Bash пытается выполнить строку с указанием интерпретатора, заканчивающуюся возвратом каретки, и, конечно, интерпретатора по этому пути не существует.
То же самое происходит в более тонких формах:
- Значения переменных среды с завершающими
\rсимволами безвозвратно нарушают сравнения строк. - Скрипты Docker
ENTRYPOINTне могут запуститься из-за повреждённого указания на интерпретатор. - Скрипты Python читают конфигурационные файлы и получают ключи вроде
"setting\r"вместо"setting". - Makefiles игнорируют правила, потому что символ таба следует за
\r.
Как обнаружить проблему
Перед тем как что-либо исправлять, подтвердите, что файл действительно имеет концы строки CRLF. Несколько быстрых способов:
cat -A (Linux/macOS)
cat -A deploy.sh
Строки, заканчивающиеся на CRLF, будут отображаться как ^M$ в конце. Строки, заканчивающиеся на LF, будут отображаться только как $.
file command
file deploy.sh
# deploy.sh: Bash script, ASCII text, with CRLF line terminators
xxd или hexdump
xxd deploy.sh | grep "0d 0a"
Любые совпадения подтверждают наличие CRLF. Если вы видите только 0a в концах строк, вы в порядке.
Как исправить проблему
dos2unix (самый быстрый способ исправления)
Установите один раз, запустите на любом файле:
# Install
sudo apt install dos2unix # Debian/Ubuntu
brew install dos2unix # macOS
# Convert a single file
dos2unix deploy.sh
# Convert all shell scripts in the repo
find . -name "*.sh" -exec dos2unix {} \;
sed (без необходимости в дополнительных инструментах)
sed -i "s/\r//" deploy.sh
tr (соответствует POSIX)
tr -d "\r" < deploy.sh > deploy_fixed.sh && mv deploy_fixed.sh deploy.sh
Как предотвратить проблему: настройка Git
Исправление файлов после того, как они уже были созданы, является реактивным решением. Постоянное решение — указать Git, как обрабатывать концы строк, чтобы проблема никогда не достигала репозитория.
Подход с использованием .gitattributes (рекомендуется)
А .gitattributes Файл, коммитируемый в репозиторий, обеспечивает единообразие концов строк для всех пользователей, независимо от их настроек локального Git. Добавьте это в корень вашего репозитория:
# Normalize all text files to LF in the repo
* text=auto eol=lf
# Explicitly enforce LF for files that must never have CRLF
*.sh text eol=lf
*.bash text eol=lf
Makefile text eol=lf
Dockerfile text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.json text eol=lf
# Binary files — do not touch
*.png binary
*.jpg binary
*.gif binary
*.zip binary
*.gz binary
The text=auto eol=lf параметр указывает Git автоматически определять текстовые файлы и хранить их с LF независимо от ОС разработчика. Явные строки ниже этого параметра фиксируют файлы, где CRLF может вызвать сбои во время выполнения.
После добавления или изменения .gitattributes, переформатируйте существующие файлы в репозитории:
git add --renormalize .
git commit -m "normalize line endings"
Настройка core.autocrlf (только локально)
Git имеет глобальную настройку, называемую core.autocrlf , которая управляет преобразованием концов строк при выдаче и коммите. Рекомендуемые значения зависят от вашей ОС:
# Windows — convert CRLF to LF on commit, LF to CRLF on checkout
git config --global core.autocrlf true
# Linux/macOS — convert CRLF to LF on commit, no conversion on checkout
git config --global core.autocrlf input
Проблема с использованием core.autocrlf один раз: она применяется только на машине, где она настроена. Разработчик, который никогда не настраивал её, может всё ещё отправлять файлы с CRLF. Файл защищён на уровне репозитория, поэтому он строго надёжнее. .gitattributes Настройки редактора, которые нужно правильно задать
Ваш редактор является первым барьером защиты. Несколько простых настроек:
Показывается конец строки для текущего файла в статус-баре (в правом нижнем углу). Нажмите на него, чтобы переключаться между CRLF и LF. Чтобы установить значение по умолчанию для новых файлов, добавьте это в ваш
VS Code
Редакторы JetBrains (IntelliJ, WebStorm, PyCharm) settings.json:
{
"files.eol": "\n"
}
Перейдите в
Настройки → Редактор → Стиль кода и установите Разделитель строк Unix и macOS (\n) к . Вы также можете добавить файлв корень репозитория: .editorconfig Большинство современных редакторов уважают
[*]
end_of_line = lf
insert_final_newline = true
по умолчанию или через плагин. Коммитирование его в репозиторий означает, что участники получают единые настройки без необходимости в ручной настройке. .editorconfig Полный чек-лист
Если вы стандартизируете репозиторий, в котором ранее возникали проблемы с концами строк, пройдите по этому списку в порядке:
файл с
- Добавьте
.gitattributesи явными правилами для скриптов shell, Dockerfiles и конфигурационных файлов.* text=auto eol=lfДобавьте файл - чтобы исправить существующие отслеживаемые файлы.
.editorconfigи явными правилами для скриптов shell, Dockerfiles и конфигурационных файлов.end_of_line = lf. - Выполните
git add --renormalize . && git commit -m "normalize line endings"на любые неотслеживаемые скрипты, которые будут выполняться на Linux. - Выполните
dos2unixглобально на машинах Linux/macOS; - Множество
core.autocrlf inputна Windows.trueДобавьте шаг проверки в CI — что-то вроде - — чтобы обнаруживать любые отклонения до того, как они достигнут деплоя.
grep -rUl $'\r' *.sh— чтобы поймать любые регрессии до их достижения в развертывании.
Установите наши расширения
Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска
恵 Табло результатов прибыло!
Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!
Подписаться на новости
все Новые поступления
всеОбновлять: Наш последний инструмент Добавлено 12 июня 2026 года
