Установка патчей в Linux – работа с утилитой patch
Практически каждый разработчик программного обеспечения (ПО), программист или верстальщик сталкивается (и довольно часто) с необходимостью модификации некоторой части рабочего проекта или даже нескольких строк кода. Особенно это актуально, когда в разработке участвует несколько человек, которые могут вносить правки в разных частях проекта. Для удобства и автоматизации действий по составлению таких правок используются специализированные утилиты. Одной из таких является утилита patch и о ней более подробно будет рассказано в данной статье.
Что такое патч?
Говоря о патчах вкупе с утилитой patch, следует подразумевать, что это касается исключительно текстовых данных. Другими словами, происходит работа с исходными кодами проекта, будь то код C++, PHP, HTML и т. д. Вообще, все самые «суровые» программисты или разработчики в процессе своей совместной работы над проектом обмениваются исключительно отдельными правками, а не пересылают друг другу актуальные версии проектов целиком.
Сама правка, т. е. текстовые изменения в исходном коде проектов (для одного его файла или сразу для нескольких) и есть патч или «заплатка». Патч, помимо самих изменений кода содержит также и некоторую служебную информацию, необходимую для правильного «наложения заплатки», т. е. для установки патча. Таким образом, патч — это текстовый файл определённого формата, содержащий в себе данные и инструкции для приведения конечного файла (или проекта) к нужному или актуальному виду.
Утилита patch умеет быстро и эффективно распоряжаться данными из файла-патча, используя для этого хранящиеся в нём инструкции. И таким образом выполняет все рутинные действия по редактированию. Пользователю (разработчику) необходимо лишь правильно выполнить соответствующую команду, задав все необходимые аргументы и опции.
Синтаксис и основные опции команды patch
Нет ничего удивительного в том, что утилита patch относится к категории ПО, которое обязательно должно быть установлено на любой машине для разработки программ, да и вообще для ведения разработки. Практически любой дистрибутив Linux предоставляет утилиту patch предустановленной по-умолчанию.
Стоит также отметить, что по своей функциональности, patch довольно сложна и обладает, без преувеличения, просто огромным набором опций. По этой причине в данной статье будут приведены только самые распространённые приёмы при работе с этой утилитой и только сопутствующие им опции команд. Синтаксис команды patch следующий:
Здесь originalfile – это файл, который необходимо «пропатчить» до актуального состояния. А patchfile – файл-патч. Сразу возникает вопрос: а откуда берётся этот файл-патч? Ответ: он генерируется другой утилитой — diff, которая находит построчные различия между файлами. Либо же патч может быть составлен вручную, автором, если он знаком с соответствующим форматом. Но это бывает крайне редко, обычно прибегают к помощи diff или её аналогов.
В следующей таблице приведены опции команды patch, которые используются наиболее часто:
Опция | Значение |
-i patchfile | Читает информацию из патч-файла, указываемого параметром patchfile. |
-r rejectfile, —reject-file=rejectfile | |
-N, —forward | Когда патч не применяется, то утилита patch обычно пытается определить, выглядит ли ситуация так, как если бы патч уже был применён. Опция -N отключает такое поведение. |
-pnum, strip=num | Обрезает части пути к файлу, разделяемые символом косой черты до уровня, указанного в параметре num. Например: p0 оставит путь /u/john/src/blurfl/blurfl.cpp неизменным, а p4 обрежет тот же путь до blurfl/blurfl.cpp. |
-o outputfile, —output=outputfile | Отправляет вывод в указываемый в параметре outputfile файл. Не следует использовать эту опцию, если в качестве outputfile указывается файл, который должен быть пропатчен. Если в качестве outputfile указать символ дефиса «-», то вывод будет направляться в стандартный поток STD_OUT. |
-E, —remove-empty-file | Удаляет файлы, оказавшиеся пустыми после применения патча. Эта опция имеет смысл, когда используемые патчи имеют не контекстный формат. |
—dry-run | Печатает результаты применения патча без реальной модификации файлов. Полезно для быстрого и безопасного тестирования патчей. |
-R, —reverse | Откатывает все изменения (если они возможны), т. е. отменяет установку патча. |
-c, —context | Интерпретирует файл патча как обычный контекстный формат, генерируемый утилитой diff. |
-b, —backup | Создаёт резервную копию оригинального файла вместо его удаления. |
Применение патчей к отдельным файлам
Прежде, чем начать рассмотрение практических примеров, необходимо сказать несколько слов о той самой утилите, которая и создаёт патчи — diff. Она может генерировать патчи трёх типов — простой, контекстный и контекстный унифицированный. Простой гораздо более компактный по размеру, чем контекстные, но последние гораздо более удобочитаемы и понятны для восприятия пользователем. Для того, чтобы сгенерировать простой патч, для команды diff никаких специальных опций не требуется. А для генерации контекстного или унифицированного контекстного патчей предназначены опции -с и -u соответственно:
Пусть имеется файл с кодом C++ ChildClass.cpp:
И пусть в этот файл было внесено следующее изменение: метод valueSqr() был переименован в calcSqr(). Тогда контекстный патч (файл contextpatch) будет выглядеть следующим образом:
Теперь, чтобы пропатчить старую версию ChildClass.cpp, нужно выполнить команду:
В результате будет получен файл ChildClass_new.cpp с актуальным содержимым.
Работа с проектами
С помощью утилиты patch можно также применять патчи для нескольких файлов, причём расположенных в разных каталогах. Это удобно, когда изменения проводятся в масштабах целого проекта. Но в этом случае и сам патч должен быть особым образом подготовлен утилитой diff.
Пусть имеется старый проект в каталоге base-project. Внутри него имеются подкаталоги include и src, в которых, в свою очередь находятся файлы с изменениями — ChildClass.h (в каталоге include) и ChildClass.cpp (в каталоге src). Сам изменённый (актуальный) проект был помещён в отдельный каталог new-project. Подготовка патча будет выглядеть следующим образом:
Сгенерированный файл-патч project-patch:
Следует обратить внимание, что в данных примерах указываются относительные пути. Файл-патч будет помещён в текущий активный каталог.
Чтобы применить патч нужно выполнить следующую команду:
Как видно, вместо ключа -i можно использовать символ «
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.
Использование комманд diff и patch
Комманды diff и patch представляют собой мощную комбинацию. Они часто используются для получения разницы между оригинальными файлами и обновленными, что бы другие люди, у которых есть только только оригинальные файлы могли обновить их используя файлы, которые содержат только разницу. В этой статье показаны базовые принципы использования этих команд.
В этой статье используются без объяснения некоторые базовые команды Linux, такие как смена смена директории, копирование файлов и редактирование текстовых файлов.
Использование diff для создания простого патча
Наиболее простой пример использования команды diff — получение различий между двумя файлами, оригинальным и обновленным. Можете, например, написать насколько слов обычного текста, сделать какие-нибудь изменения, и сохранить измененния во второй файл. Теперь вы можете сравнить эти эти два файла, используя команду diff:
Конечно, надо заменить originalfile и updatedfile соответствующими именами файлов. В результате должно получиться что-то вроде этого:
Обратите внимание: Что бы продемонстрировать создание простого патча, я использовал оригинальный файл, содержащий строку «These are a few words.», и измененный файл, содержащий строку «These still are just a few words.» Вы можете создать эти файлы сами, если хотите запустить команду из статьи и получить тот же результат.
1c1 показывает номер строки и то, что с ней надо сделать. Обратите внимание, что может быть сразу несколько строк(например, 12,15, что означает со строки 12 до строки 15). Символ «c» означает, что патч заменит эту строку. Есть еще два других символа: «a» и «d». Они означают «добавить»(add) и «удалить»(delete) соответственно. Таким образом, синтаксис следующий: (номер строки или диапазон строк)(c,a или d)(номер строки или диапазон строк), хотя когда используются «a» или «d», одна из частей (номер строки или диапазон строк) может содержать только номер одной строки.
Когда используется «c», номера строк слева — это строки в оригинальном файле, которые надо заменить строками, находящимися в патче, а номера строк справа — это строки, которые должны быть в пропатченном файле.
Когда используется «a», номер слева может быть только номером одной строки, который показывает, где надо добавить строку в пропатченном файле, а номера строк справа — это строки, которые должны быть в пропатченном файле.
Когда используется «d», номера строк слева — это строки, которые надо удалить, чтобы получить пропатченную версию файла, а номер строки справа может быть только номером одной строки, который показывает где будут строки в пропатченном файле, если они не будут удалены. Вы можете подумать, что последний номер не нужен, но не забывайте, что патч можно применить для восстаноления исходного файла. Это будет объяснено позже.
Знак » » означает, что символы после этого знака надо добавить. Когда надо заменить строки («c» между номерами строк), вы увидите оба знака: и » «. Когда надо добавить строку («a» между номерами строк), вы увидите только знак «>», а когда надо удалить строку («d» между номерами строк), вы увидите только знак » » работает со всеми командами. Это очень полезное свойство.
Применение простого патча, который мы создали
Мы можем использовать патч, который только что создали, чтобы получить из оригинального файла обновленный. Для этого надо скопировать оригинальный файл и патч в одно и тоже место. И затем применить патч:
Естественно, и здесь надо изменить имена файлов на необходимые. Если все прошло хорошо, должен получиться файл, идентичный обновленному. Вы можете убедиться в этом, используя команду diff с опцией «-s»:
Замените текст между [ и ] на путь к оригинальному файлу. Например, если обновленный файл, который вы использовали при создании патча находится в родительской директории вышай текущей, то «[/path/to/the/original/updatedfile]» надо заменить на «..» (bash понимает это как родительскую директорию от текущей). И конечно надо изменить имена файлов на верные.
Поздравляю! Если diff сообщила, что файлы идентичные, вы только что успешно создали и применили патч! Однако формат патча, который мы только что использовали не единственный. В следующей главе мы рассмотрим другой формат патча.
В первой главе мы создали патч, используя нормальный формат команды diff. Однако этот формат не обеспечивает контекстной зависимости, а использует строки целиком. Создадим патч для того же файла, но используя контектсный формат:
Результат получится следующий:
Как вы видите, здесь включено имя файла. Это значит, что нам не придется набирать его во время применения патча. Далее идет дата и время последнего изменения файла. строка с 15 «*» показывает начало изменений. Они показывают, что надо сделать со следующим блоком текста. Два номера 1 — это номера строк (здесь тоже может быть сразу несколько строк), а «!» означает, что строки надо заменить. Строка с «!» перед тремя «-» должна быть заменена второй строкой с «!», которая идет после трех «-«(конечно сам ! не будет включен; это синтаксис контекстного формата). Как вы можете видеть, здесь нет знаков «c», «a» и «d».Действие, которое нужно сделать, определяется символом в начале строки. «!» означает замену. Другие символы — «+», «-» и » » (пробел). «+» означает добавление, «-» означает удаление, а » » означает ничего не делать: патч использует его чтобы убедиться, что он изменяет правильную часть файла.
Применять этот патч легче: при тех же условиях, что и для предыдущего патча (записываем вывод команды diff в файл, затем копируем патч и оригинал в одно и то же место), надо выполнить следующую команду:
Вы возможно сейчас думаете: зачем нам надо указывать имя нового файла? Это надо сделать из-за того, что патч старается изменить существующий файл, а не создает новый. Это удобно при создании патча для нескольких файлов сразу. Это приводит нас к следующей цели: создание патча для дерева файлов. Рассмотрим это в следующей главе.
Получение различий между несколькими файлами
Наиболее простой способ получить различия между несколькими файлами — это положить их в одну директорию и выполнить команду diff для этой директории целиком. Вы можете просто передать команде diff в качестве параметров имена директорий вместо имен файлов:
Обратите внимание: Если в директория есть поддиректории, то надо использовать опцию «-r».
В результате должно получится что-то вроде этого:
Обратите внимание: Я создал несколько несколько файлов для примера. Вы можете скачать архив, содержащий эти файлы: http://www.linuxtutorialblog.com/resource/uploads/diffpatchexamplefiles.tar.gz.
Как вы видите, нормальный формат содержит только имена файлов и изменяемые строки.
Теперь используем контекстный формат:
Первая вещь, которую вы должны были заметить — это увеличение размера; контекстный формат содержит больше информации, чем нормальный. Этого не было заметно в первом премере, так как не было контекста. Однако теперь контекст есть, и за счет него размер патча увеличился. Кроме того, вы наверное заметили, что имя файла повторяется дважды. Это возможно сделано для того, чтобы легче было понять когда начался патч следующего файла или для обеспечения лучшего восстановления.
Другой способ получить разницу между между несколькими файлами — это написать скрипт, который выполняет команду diff несколько раз и добавляет результат выполнения в один файл. Мы не будем рассматривать этот способ, так как положить все файлы в одну директорию горазда проще.
Создать патч было легко, но использование директорий ставит следующую проблему: бедут ли патч изменять только соответствующие файлы в текущей директории, или будет использовать соответствующий путь, указанный в файле? Чтобы узнать это, смотрите следующую главу!
Применение патча к нескольким файлам
В предыдущей главе мы создали патч для нескольких файлов, воспользовавшись следующей командой:
Обратите внимание: мы используем контекстный формат патча, так как это является хорошим тоном.
Теперь надо использовать полученный патч. Скопируйте оригинальную директорию и патч куда-нибудь и примените следующую команду:
Однако возникает ошибка, что невозможно найти файлы для патча. Команда пытается найти файл file1 в текущей директории (по умолчанию патч убирает все пути перед именем файла). И конено файла нет, так как мы пытаемся обновить файлы в директории originaldirectory. Поэтому мы должны заставить патч использовать полный путь. Это делается следующим образом:
Обратите внимание: Вы может подумать, что можно просто переместиться в originaldirectory и запустить патч. Но это не так! Так делать не стоит: если в в патче содержатся поддиректории, то он будет искать их в рабочей директории, и не найдет, или найдет не те. Используйте опцию «-p», чтобы заставить патч искать файлы в поддиректориях.
Опция «-p» говорит патчу сколько слэшей (включая то, что перед ними, обычно директории) нужно вырезать перед именем файла (обратите внимание, что при использовании опции «-p0», патч будет будет искать файлы и в originaldirectory и в updateddirectory).Когда мы устанавливаем 0, это означает что не надо удалять пути, но можно поставить 1, чтобы удалить первый слэш, или 2, чтобы удалить два слэша, и т.д. Это может быть полезно, если если в патче используется структура каталогов, отличная от вашей. Например, если в патче используется следующая структура каталогов:
Вам надо просто посчитать количество слэшей (/ (1) home/ (2) username/ (3) sources/ (4) program/ (5)) и передать это число в опцие «-p». Если вы используете «-p5», то патч будет искать и в originaldirectory/file1 и в updateddirectory/file1. Не забудьте, что патч рассматривает два слэша друг за другом (как в /home/username//sources) как один. Это вызвано тем, что иногда патч скрипты добавляют дополнительный слэш между директориями.
Восстановление оригинального файла из пропатченного
Иногда возникает необходимость восстановить оригинальный файл из пропатченного. Например, если в нем содержится ошибка. Для этого надо использовать опцию «-R»:
Обычно эта операция проходит без ошибок, и вы получаете исходный файл. Однако лучше на всякий случай сделать резервную копию перед восстанолением файлов.
Есть еще один формат вывода различий командой diff: унифицированный формат. Он более компактен, так как содержит уменьшенные контекстные строки. Однако он поддерживается только GNU diff и patch. Если вы его используете, вы должны быть уверены, что у пользователей, для которых патч предназначен, GNU patch. Linux допускает использование этого формата.
Унифицированный формат похож на контекстный, но это не одно и тоже. Патч в унифицированном формате можно создать так:
Результат будет седующий:
Как вы видите, номера строк заключены между «@». Кроме того, есть дополнительный пробел после «+» или «-«. Это экономит несколько байт. Другое различие: в унифицированном формате нет специального знака для замены. Он просто удаляет старые строки («-«) и добавляет новые («+»). Разница между этими действиями заключается в том, что при замене используется один и тот же номер строки, а при удалении и добавлении разные.
Читая про три разных формата, вы вероятно задумались: а какой же выбрать? Вот небольшое сравнение:
Нормальный формат наиболее совместимый. Любые команды похожие на diff/patch должны понять его. Его недостаток — это отсутствие контекста.
Контекстный формат широко распространен, но не все команды его понимают. Его преимущество в наличии контекста.
Унифицированный формат тоже включает контекст, и при этом более компактем. Но его поддерживает только GNU diff and patch.
Если вы уверены, что патч буду использовать только пользователи с GNU diff/patch, то лучше всего выбрать унифицированный формат, так как он более компактный. В большинстве других случаев лучший выбор — это контекстный формат. Нормальный формат следует использовать если вы уверены, что пользователь будет применять патч командами, не поддерживающими контекстный формат.
Изменение количества контекстных строк
Можно заставить команду diff включать в патч сеньшее количество строк контекста, чем должно быть. В больших патчах это может сильон уменьшить его размер. Однако если уменьшить количество контекстных строк, это может привести в неработоспособности патча. Цитати из справки GNU diff: «Для большинства операций в патче должно быть хотя бы две строки контекста.»
Указать количество контестных строк можно несколькими способами:
Если вы хотит использовать контекстный формат, вы можете вы можете совместить эти указания, добавив в опцию «-C». Пример:
]$ diff -C 2 originaldirectory/ updateddirectory/
Предыдущая команда будет использовать контекстный формат с двумя контекстными строками.
Если вы хотит использовать контекстный формат, вы можете вы можете совместить эти указания, добавив в опцию «-U». Пример:
]$ diff -U 2 originaldirectory/ updateddirectory/
Предыдущая команда будет использовать унифицированный формат с двумя контекстными строками.
Если не указывать какой формат вы хотите использовать, то команда будет выглядеть примерно так:
]$ diff -2 originaldirectory/ updateddirectory/
Однако это будет работать только если вы определите формат. Вам необходимо использовать эту опцию или с «-c» или с «u».
Несмотря на то, что эта статья описывает множество особенностей работы команд diff и patch, она не может описать все их возможности. Если вы хотите узнать больше об этих командах, вы можете прочитать страницу помощи по этим командам и документацию GNU.