← Весь справочник

diff, cmp и patch

Сравнить файлы и каталоги построчно или побайтно, сделать патч и применить его

diff сравнивает два файла построчно и печатает, что поменять в первом, чтобы получился второй. С -r сравнивает целые каталоги. Вывод в формате -u (unified) — это и есть «патч»: его читают люди на ревью и применяет команда patch. cmp сравнивает файлы побайтно — для бинарных данных и быстрой проверки «одинаковы ли».

Синтаксис

diff [опции] старый новый
diff -r [опции] старый_каталог новый_каталог
cmp [опции] файл1 файл2 [пропуск1 [пропуск2]]
patch [опции] [файл] < изменения.patch
patch -p1 < изменения.patch          # из корня проекта

Вместо файла можно дать - (стандартный ввод) или подстановку процесса: diff <(команда1) <(команда2).

Коды возврата

Кодdiffcmp
0файлы одинаковыфайлы одинаковы
1есть различияесть различия
2ошибка (нет файла, нет прав)ошибка

1 — не ошибка, а результат. В if diff -q a b >/dev/null; then … ветка then выполнится, когда файлы одинаковы.

Формат вывода

ОпцияФормат
(без опций)«обычный»: команды вида 2c2, 4a5, 3d2 и строки с < (из первого файла) и > (из второго)
-u, -U Nunified: N строк контекста вокруг изменения (по умолчанию 3) — стандарт для патчей и ревью
-c, -C Ncontext: старый формат с *** и ---, встречается редко
-yдве колонки рядом: | — строка изменена, < — только слева, > — только справа
-W Nширина вывода для -y (по умолчанию 130)
--suppress-common-linesв -y не показывать одинаковые строки
-qтолько сказать, различаются ли файлы, без подробностей
-sсообщить и об одинаковых файлах (Files a and b are identical)
--color=alwaysраскрасить вывод (auto — только в терминале)
--label имяимя файла в заголовке -u вместо настоящего (дважды — для обоих)

Как читать unified diff

--- a      2026-10-13 15:20:41 +0000      ← старый файл и время изменения
+++ b      2026-10-13 15:20:41 +0000      ← новый файл
@@ -1,4 +1,5 @@                           ← ханк: в старом с 1-й строки 4 строки, в новом с 1-й — 5
 one                                      ← пробел: строка без изменений (контекст)
-two                                      ← минус: строка удалена
+2                                        ← плюс: строка добавлена
 three
 four
+five

Изменённая строка — это пара «- старая, + новая». Строка \ No newline at end of file значит, что у предыдущей строки нет \n в конце. @@ -1 +0,0 @@ значит: в новом файле на этом месте строк нет (файл удалён или опустел).

Что игнорировать при сравнении

ОпцияЧто игнорирует
-wвсе пробелы и табуляции: xy и x y равны
-bизменение количества пробелов (и пробелы в конце строки), но не их появление: x y = x y, но xy ≠ x y
-Zтолько пробелы в конце строк
-Bдобавленные и удалённые пустые строки
-iрегистр букв
-I регуляркаизменения, в которых все строки подходят под регулярку (например, -I '^#' — комментарии)
--strip-trailing-cr\r в конце строк (файлы из Windows)
-aсчитать файлы текстом, даже если похожи на бинарные

Каталоги

ОпцияЧто делает
-rсравнивать каталоги рекурсивно
-qвместе с -r: только список отличающихся файлов и Only in …
-Nотсутствующий файл считать пустым: вместо Only in будет полный diff (нужно для патча, который создаёт и удаляет файлы)
-x 'шаблон'исключить файлы по шаблону имени (можно несколько раз)
-X файлвзять шаблоны исключений из файла

cmp: побайтное сравнение

ОпцияЧто делает
(без опций)первое отличие: a b differ: char 2, line 1 (номер байта с 1)
-lвсе отличающиеся байты: номер байта (десятичный) и оба значения в восьмеричном виде
-bпоказать сами отличающиеся байты как символы
-sничего не печатать, только код возврата
-n Nсравнить не больше N байт
-i Nпропустить первые N байт в обоих файлах (-i N:M — разные пропуски)

Если один файл — начало другого, cmp пишет EOF on a after byte 2 и возвращает 1.

patch: применить изменения

ОпцияЧто делает
-pNотрезать N первых каталогов от путей в патче: для --- a/src/x.c нужен -p1, чтобы получилось src/x.c
--dry-runтолько проверить, применится ли, ничего не меняя
-Rоткатить патч (применить в обратную сторону)
-i файлвзять патч из файла вместо стандартного ввода
-bсохранить исходник как файл.orig
-Nпропускать уже применённые ханки, не спрашивая
-o файлзаписать результат в другой файл
-d каталогперейти в каталог перед применением

Если файл с тех пор немного сдвинулся, patch найдёт место сам и сообщит Hunk #1 succeeded at 4 (offset 2 lines). Ханки, которые не легли, он пишет в файл.rej.

Примеры

printf 'one\ntwo\nthree\nfour\n' > a.txt
printf 'one\n2\nthree\nfour\nfive\n' > b.txt

# обычный и unified-вывод
diff a.txt b.txt
diff -u a.txt b.txt

# рядом, только различия
diff -y --suppress-common-lines -W 40 a.txt b.txt

# одинаковы ли — в скрипте
if diff -q a.txt b.txt >/dev/null; then echo same; else echo differ; fi

# сравнить вывод двух команд без временных файлов
diff <(printf 'a\nb\n') <(printf 'a\nc\n')

# каталоги: список различий, без временных файлов
mkdir -p old new && echo 1 > old/x && echo 2 > new/x && echo n > new/added
diff -rq -x '*.tmp' old new

# сделать патч на каталог и применить его к копии
diff -ruN old new > change.patch
cp -r old work && cd work
patch -p1 --dry-run < ../change.patch   # проверить
patch -p1 < ../change.patch             # применить
patch -p1 -R < ../change.patch          # откатить
cd ..

# патч на один файл
diff -u a.txt b.txt > ab.patch
patch a.txt < ab.patch

# побайтно: первое отличие и все отличия
printf 'hello\n' > h1; printf 'hallo!\n' > h2
cmp h1 h2
cmp -l h1 h2
cmp -s h1 h2 && echo same

Подводные камни

  • Код 1 под set -e остановит скрипт. diff с различиями — это «неуспех» для оболочки. Пиши diff a b || true или проверяй код в if.
  • Порядок аргументов важен. diff старый новый: строки с -/< — из первого файла. Перепутанные аргументы дают «обратный» патч, и patch предложит применить его с -R (Reversed (or previously applied) patch detected!).
  • Без -N новые и удалённые файлы не попадут в патч: diff -r скажет только Only in new: added, а patch такую строку пропустит.
  • Неверный -p даёт can't find file to patch … Perhaps you used the wrong -p. Патчи из git diff и diff -ru old new применяют из корня проекта с -p1. Сначала всегда --dry-run.
  • patch задаёт вопросы при сомнениях (Assume -R? [n], File to patch:). В терминале он ждёт ответа, без терминала молча берёт ответ по умолчанию «нет» и пропускает ханк. В скрипте указывай явно: -N — пропускать уже применённое, -R — откатывать, и проверяй код возврата (1 — не все ханки легли).
  • \r в конце строк делает все строки разными. Если файл пришёл из Windows, добавь --strip-trailing-cr или убери \r заранее (tr, sed 's/\r$//').
  • -b — это не «игнорировать все пробелы». xy и x y для -b различаются, для -w — нет.
  • cmp -l печатает байты в восьмеричном виде: 2 145 141 — это e (0x65) против a (0x61), а номер байта — десятичный.
  • diff текстовый. На бинарных файлах он скажет только Binary files a and b differ; для байтов бери cmp или сравни дампы: diff <(xxd a) <(xxd b).

См. также

comm · xxd и od · git · контрольные суммы · sort