Первый запуск и как прочитать результат
Допустим, у вас две версии конфигурационного файла и нужно понять, что изменилось. Глазами по длинным текстам это делать долго и ненадёжно, а diff показывает только отличающиеся строки. Для опыта создайте два небольших файла: printf 'a\nb\nc\n' > one.txt и printf 'a\nB\nc\nd\n' > two.txt. Затем выполните diff one.txt two.txt.
Классический формат вывода использует номера строк и команды вроде c (change), a (add) и d (delete): например, 2c2 говорит, что вторая строка первого файла заменена второй строкой второго. Строки со знаком «меньше» относятся к первому файлу, со знаком «больше» ко второму. Этот формат компактен, но привыкнуть к нему сложнее, чем к следующему.
Если файлы одинаковы, diff ничего не печатает. Это не сбой, а нормальный результат, и именно по молчанию вы узнаёте, что различий нет. Результат сравнения доступен и числом: после выполнения команды переменная $? хранит код возврата.
- 0 — файлы совпадают;
- 1 — найдены различия;
- 2 — произошла ошибка, например файла нет или нет прав на чтение.
Эти коды позволяют использовать diff в скриптах с условиями if, не разбирая текст вывода.
Формат -u: плюсы и минусы строк
Чаще всего пользуются унифицированным форматом, который включается ключом -u. Его же ожидают многие программы для обмена правками. Запустите diff -u one.txt two.txt и посмотрите на структуру вывода.
Сначала идут две строки заголовка: имена файлов, перед первым стоят три минуса, перед вторым три плюса, рядом время изменения. Затем следуют блоки, каждый открывается строкой с символами @@ и диапазонами номеров строк. Внутри блока:
- строка с минусом в начале есть в первом файле, но отсутствует во втором;
- строка с плюсом появилась во втором файле;
- строка с пробелом в начале одинакова в обоих и показана для контекста.
В нашем примере вы увидите, что строка b убрана, строка B добавлена, а строка d добавлена в конце. Замена строки в этом формате обычно выглядит как пара «минус старая, плюс новая».
Контекста в формате -u по умолчанию показывается по три строки до и после изменения, а число меняется ключом -U (например, -U 1); сверьтесь со справкой своей версии. Для просмотра объёмных результатов пропустите вывод через less. В новых версиях GNU diff есть ключ --color для подсветки, но он поддерживается не везде, поэтому проверяйте diff --help в своей системе.
Ключи, которые нужны чаще всего
Базовый вызов хорошо дополняют несколько ключей. Все они перечислены в справке diff и работают в GNU-версии на Linux и в версии для macOS, но проверка своей системы не помешает.
| Ключ | Что делает |
|---|---|
| -u | унифицированный формат со знаками + и - |
| -q | не показывает разницу, только сообщает, отличаются ли файлы |
| -r | рекурсивно сравнивает каталоги |
| -i | игнорирует различия в регистре букв |
| -w | игнорирует пробелы и табуляции |
| -x шаблон | исключает файлы по шаблону при сравнении каталогов |
Ключ -w выручает, когда файл прогнали через редактор, поменявший отступы: без него diff покажет изменённой каждую строку. Ключ -i подходит для сравнения текстов, где регистр не важен.
Для двоичных файлов построчное сравнение бессмысленно. diff в таком случае сообщает только, что файлы различаются. Если нужно узнать, совпадают ли они байт в байт, используют команду cmp или сравнивают контрольные суммы, как показано в статье про md5sum и shasum.
Сравнение двух папок
Чтобы сравнить каталоги, добавьте -r. Команда diff -rq dir1 dir2 даёт короткий отчёт: в нём перечислены файлы, которые различаются, а также файлы, имеющиеся только в одном из каталогов, в формате «Only in имя каталога: имя файла». Такой обзор помогает быстро оценить масштаб расхождений перед сверкой деталей.
Если опустить -q и добавить -u, вы получите различия по каждому файлу. Для больших деревьев вывод получается длинным, поэтому сохраняйте его в файл через перенаправление или используйте tee, а затем просматривайте постепенно.
Полезный сценарий: вы сделали копию папки и хотите проверить, идентична ли она оригиналу. Запустите diff -rq оригинал копия. Пустой вывод и код возврата 0 означают, что набор имён и содержимое файлов совпадают, а код 1 говорит о различиях. Для сравнения прав, владельцев и времён diff не подходит: он смотрит на содержимое и наличие имён. Для метаданных пригодится stat.
Для плановой синхронизации деревьев удобнее rsync с пробным запуском: он сразу показывает, что передать, а не просто констатирует различие, и об этом есть отдельные материалы.
Сравнение вывода двух команд без временных файлов
Иногда нужно сравнить не файлы, а результаты команд: список пакетов до и после обновления, содержимое каталога на двух дисках, вывод ls в разные моменты. Создавать для этого временные файлы необязательно. В оболочках bash и zsh есть подстановка процесса, которая выглядит как <(команда) и подставляет вывод команды как файл. Пример: diff <(ls папка1) <(ls папка2) сравнит списки имён в двух папках.
Для оболочек попроще подстановки нет, и тогда выручают временные файлы или перенаправление вывода в файлы с последующим сравнением.
Несколько практических замечаний:
- у сравнения вывода двух команд тоже есть код возврата, поэтому его можно использовать в проверках;
- сортировка перед сравнением убирает различия в порядке строк: diff <(sort a.txt) <(sort b.txt);
- если сравниваются списки по тексту, различия в окончаниях строк (Windows и Unix) иногда делают все строки «разными»: в таком случае проверьте формат файлов командой file или поищите в справке ключ для игнорирования (в GNU diff это --strip-trailing-cr, в других версиях его может не быть).
Изменения, найденные diff, не применяются сами. Для их автоматического наложения существует отдельная команда patch, но это уже тема, выходящая за рамки статьи.