Бывает, что два файла на вид совпадают до символа, а diff сообщает, что изменились все строки или только первая и последняя. Обычно причина — символы, которых не видно на экране. В этой статье собраны виды таких «невидимых различий» и способы с ними справиться.
Символы перевода строки: CRLF, LF, CR
В разных операционных системах приняты разные способы перевода строки.
| Обозначение | Байты | Где встречается |
|---|---|---|
| LF | 0A (\n) | Linux, macOS, большинство инструментов разработки |
| CRLF | 0D 0A (\r\n) | Многие программы Windows, например Блокнот; интернет-протоколы вроде электронной почты и HTTP |
| CR | 0D (\r) | Очень старые Mac OS (до версии 9) |
Если один файл сохранён с CRLF, а другой с LF, при построчном сравнении в конце каждой строки различием оказывается невидимый \r, и кажется, будто изменилось всё. В GNU diff это различие игнорируется опцией --strip-trailing-cr, в git diff — опцией --ignore-cr-at-eol.
Перевод строки в конце файла
По соглашению POSIX строки текстового файла заканчиваются символом перевода строки. Поэтому многие инструменты ставят перевод строки и после последней строки, но некоторые редакторы этого не делают. В unified diff это различие выглядит так:
@@ -1,2 +1,2 @@ host=example.com-port=8080\ No newline at end of file+port=8080
\ No newline at end of file означает, что в конце строки прямо над пометкой нет перевода строки. В примере содержимое одинаково, а завершающий перевод строки добавлен только в файл после изменения. Многие руководства по стилю кода требуют перевод строки в конце файла, и его можно унифицировать настройкой редактора «вставлять перевод строки в конце файла».
BOM: когда отличается только первая строка
Иногда в начале файла UTF-8 стоят три байта EF BB BF. Это метка порядка байтов (BOM). Для UTF-8 порядок байтов не нужен, но некоторые программы добавляют её как пометку «этот файл в UTF-8». BOM на экране не виден, поэтому если diff сообщает, что отличается только первая строка, подозревайте BOM. Этот сайт удаляет BOM в начале файла при загрузке, а при включённом игнорировании пробелов считает пробелом и BOM (U+FEFF) во вставленном тексте.
Табуляции, пробелы и особые пробелы
- Табуляция и пробелы: одна табуляция и четыре пробела выглядят на экране одинаково, но это разные символы.
- Неразрывный пробел (NBSP, U+00A0): часто попадает в текст, скопированный с веб-страниц и из текстовых редакторов.
- Идеографический (полноширинный) пробел (U+3000): может появиться при вводе с китайской, японской или корейской раскладкой.
- Пробел нулевой ширины (U+200B): не имеет ширины и совсем не виден, но считается символом.
Большую часть таких символов отсеивают опции игнорирования пробелов (однако символы, которые не относятся к пробельным, например U+200B, могут остаться и при включённой опции). Если переключить сравнение внутри строки на символы, подсветка покажет, где именно стоит невидимый символ.
Нормализация Юникода: одинаковые символы, разные байты
Букву «é» можно записать одним символом (U+00E9) или сочетанием буквы e (U+0065) и комбинируемого знака ударения (U+0301). Так же и корейский слог «가» бывает одним символом (U+AC00) или сочетанием отдельных букв (U+1100 и U+1161). Юникод называет первую форму NFC, а вторую — NFD (UAX #15). На экране такие тексты выглядят одинаково, но при сравнении это разные символы. Типичный пример — имена файлов, созданных в macOS: в других системах они иногда оказываются в разложенной форме и не совпадают с внешне идентичными именами. Решение — перед сравнением привести обе стороны к NFC.
Как унифицировать переводы строк в git
Если с репозиторием работают в разных операционных системах, переводы строк можно унифицировать настройками git.
core.autocrlf: значениеtrueпри checkout преобразует LF в CRLF, а при commit — CRLF в LF (обычно на Windows). Значениеinputпреобразует CRLF в LF только при commit..gitattributes: правила задаются на уровне репозитория, поэтому результат одинаков, даже если настройки у людей различаются.
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
text=auto нормализует переводы строк в LF внутри репозитория для файлов, которые git считает текстовыми, а eol=lf и eol=crlf задают перевод строки при выгрузке файлов в рабочий каталог.
Попробуйте в этом инструменте
Опция Нормализовать концы строк в сравнении текстов включена по умолчанию: CRLF, CR и LF считаются одним и тем же переводом строки, а наличие перевода строки в конце файла игнорируется. Если выключить её, CR в конце строки будет показан как ␍, а строка без завершающего перевода строки — как ∅, и невидимые различия станут заметны. Опции пробелов описаны в статье Опции игнорирования пробелов, а значения символов в результате — в статье Как читать unified diff.