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).
Коды возврата
| Код | diff | cmp |
|---|---|---|
0 | файлы одинаковы | файлы одинаковы |
1 | есть различия | есть различия |
2 | ошибка (нет файла, нет прав) | ошибка |
1 — не ошибка, а результат. В if diff -q a b >/dev/null; then … ветка then выполнится, когда файлы одинаковы.
Формат вывода
| Опция | Формат |
|---|---|
| (без опций) | «обычный»: команды вида 2c2, 4a5, 3d2 и строки с < (из первого файла) и > (из второго) |
-u, -U N | unified: N строк контекста вокруг изменения (по умолчанию 3) — стандарт для патчей и ревью |
-c, -C N | context: старый формат с *** и ---, встречается редко |
-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