HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Идемпотентность помогает проектировать более надёжные системы. Это математическая концепция, которую должен понимать каждый разработчик. Операция считается идемпотентной, если её многократное выполнение приводит к тому же результату, что и однократное выполнение. Например, умножение на 1 - идемпотентная операция.</p>
1 <p>Идемпотентность помогает проектировать более надёжные системы. Это математическая концепция, которую должен понимать каждый разработчик. Операция считается идемпотентной, если её многократное выполнение приводит к тому же результату, что и однократное выполнение. Например, умножение на 1 - идемпотентная операция.</p>
2 <p>x * 1 == x * 1 * 1</p>
2 <p>x * 1 == x * 1 * 1</p>
3 <p>Умножение на ноль - тоже идемпотентная операция.</p>
3 <p>Умножение на ноль - тоже идемпотентная операция.</p>
4 <p>x * 0 == x * 0 * 0</p>
4 <p>x * 0 == x * 0 * 0</p>
5 <p>Ключевая концепция, которую нужно запомнить: однократное выполнение операции может иметь побочные эффекты, но повторное выполнение этой же операции не приведёт ни к чему другому, кроме того, что было сделано при первом выполнении. То есть присваивание - идемпотентная операция.</p>
5 <p>Ключевая концепция, которую нужно запомнить: однократное выполнение операции может иметь побочные эффекты, но повторное выполнение этой же операции не приведёт ни к чему другому, кроме того, что было сделано при первом выполнении. То есть присваивание - идемпотентная операция.</p>
6 <p>x := 4</p>
6 <p>x := 4</p>
7 <p>Вы можете присваивать x значение 4 сколько угодно раз, но x всё равно будет иметь значение 4. При этом присваивание x значения 4 один раз отличается от нуля.</p>
7 <p>Вы можете присваивать x значение 4 сколько угодно раз, но x всё равно будет иметь значение 4. При этом присваивание x значения 4 один раз отличается от нуля.</p>
8 <h2>Содержание</h2>
8 <h2>Содержание</h2>
9 <ul><li><a>Идемпотентность HTTP-методов</a></li>
9 <ul><li><a>Идемпотентность HTTP-методов</a></li>
10 <li><a>Очереди сообщений</a></li>
10 <li><a>Очереди сообщений</a></li>
11 <li><a>Убедитесь в идемпотентности задач, которые выполняете через воркер</a></li>
11 <li><a>Убедитесь в идемпотентности задач, которые выполняете через воркер</a></li>
12 <li><a>SQL-миграции</a></li>
12 <li><a>SQL-миграции</a></li>
13 <li><a>Денормализованные данные</a></li>
13 <li><a>Денормализованные данные</a></li>
14 <li><a>Как писать идемпотентные bash-скрипты</a></li>
14 <li><a>Как писать идемпотентные bash-скрипты</a></li>
15 <li><a>Bash-идиомы</a></li>
15 <li><a>Bash-идиомы</a></li>
16 <li><a>Завершение</a></li>
16 <li><a>Завершение</a></li>
17 </ul><h2>Идемпотентность HTTP-методов</h2>
17 </ul><h2>Идемпотентность HTTP-методов</h2>
18 <p>HTTP-методы могут быть идемпотентными или нет.</p>
18 <p>HTTP-методы могут быть идемпотентными или нет.</p>
19 <p>DELETE - идемпотентный метод. Вы можете сколько угодно раз использовать DELETE, но результат будет всегда таким же, как после первого выполнения операции. Например,<em>DELETE /users/4/contacts/3</em>удаляет контакт с ID 3. Если вы выполните эту же операцию ещё раз, ничего не произойдёт, так как контакт уже удалён.</p>
19 <p>DELETE - идемпотентный метод. Вы можете сколько угодно раз использовать DELETE, но результат будет всегда таким же, как после первого выполнения операции. Например,<em>DELETE /users/4/contacts/3</em>удаляет контакт с ID 3. Если вы выполните эту же операцию ещё раз, ничего не произойдёт, так как контакт уже удалён.</p>
20 <p>GET - тоже идемпотентный метод. Это даже не просто идемпотентный, но ещё и безопасный метод. А безопасные методы можно сравнить с умножением на единицу. Умножать на 1 можно сколько угодно раз, результат всегда будет одинаковым. GET просто получает ресурс. Например, никогда не стоит использовать нормальные ссылки для удаления ресурсов.</p>
20 <p>GET - тоже идемпотентный метод. Это даже не просто идемпотентный, но ещё и безопасный метод. А безопасные методы можно сравнить с умножением на единицу. Умножать на 1 можно сколько угодно раз, результат всегда будет одинаковым. GET просто получает ресурс. Например, никогда не стоит использовать нормальные ссылки для удаления ресурсов.</p>
21 <p>POST не относится к идемпотентным методам. При каждом выполнении POST можно ждать побочных эффектов. Например, когда вы используете POST для отправки данных из контактной формы, отправляется письмо.</p>
21 <p>POST не относится к идемпотентным методам. При каждом выполнении POST можно ждать побочных эффектов. Например, когда вы используете POST для отправки данных из контактной формы, отправляется письмо.</p>
22 <p>Потребители и провайдеры используют эту концепцию, когда речь идёт об API. Дизайн с учётом этой концепции позволяет соблюдать правило наименьшего удивления.</p>
22 <p>Потребители и провайдеры используют эту концепцию, когда речь идёт об API. Дизайн с учётом этой концепции позволяет соблюдать правило наименьшего удивления.</p>
23 <blockquote><p><strong>NB! Правило наименьшего удивления или principle of least astonishment гласит, что результат выполнения операции должен быть очевидным и предсказуемым по названию операции. Полезно ознакомиться со статьями<a>"Именование в программировании"</a>и<a>"Ошибки именования в программировании"</a>.</strong></p>
23 <blockquote><p><strong>NB! Правило наименьшего удивления или principle of least astonishment гласит, что результат выполнения операции должен быть очевидным и предсказуемым по названию операции. Полезно ознакомиться со статьями<a>"Именование в программировании"</a>и<a>"Ошибки именования в программировании"</a>.</strong></p>
24 </blockquote><h2>Очереди сообщений</h2>
24 </blockquote><h2>Очереди сообщений</h2>
25 <p>Представьте, что разрабатываете веб-приложение для управления мероприятиями. В нём есть функция добавления пользователей в список приглашённых на то или иное мероприятие. То есть на одно мероприятие можно пригласить сколько угодно людей. Для ускорения процесса вы решаете отправлять приглашения через воркер. Когда пользователь оформляет мероприятие, сообщения отправляются в очередь. Воркер получает эти сообщения и отправляет приглашения на мероприятия.</p>
25 <p>Представьте, что разрабатываете веб-приложение для управления мероприятиями. В нём есть функция добавления пользователей в список приглашённых на то или иное мероприятие. То есть на одно мероприятие можно пригласить сколько угодно людей. Для ускорения процесса вы решаете отправлять приглашения через воркер. Когда пользователь оформляет мероприятие, сообщения отправляются в очередь. Воркер получает эти сообщения и отправляет приглашения на мероприятия.</p>
26 <p>Это очень распространённый паттерн, и, если вы его ещё не используете, стоит подумать о том, чтобы начать.</p>
26 <p>Это очень распространённый паттерн, и, если вы его ещё не используете, стоит подумать о том, чтобы начать.</p>
27 <p>В какой-то момент вы замечаете проблемы с SMTP и понимаете, что ваши письма в какой-то момент перестали отправляться. Логи не позволяют определить этот момент или понять, какие письма не отправлены. Вы решаете вызвать функцию, которая снова отправит письма. Но понимаете, что не знаете, для каких мероприятий нужно вызывать эту функцию.</p>
27 <p>В какой-то момент вы замечаете проблемы с SMTP и понимаете, что ваши письма в какой-то момент перестали отправляться. Логи не позволяют определить этот момент или понять, какие письма не отправлены. Вы решаете вызвать функцию, которая снова отправит письма. Но понимаете, что не знаете, для каких мероприятий нужно вызывать эту функцию.</p>
28 <p>Здесь в игру вступает идемпотентность.</p>
28 <p>Здесь в игру вступает идемпотентность.</p>
29 <h2>Убедитесь в идемпотентности задач, которые выполняете через воркер</h2>
29 <h2>Убедитесь в идемпотентности задач, которые выполняете через воркер</h2>
30 <p>Вернёмся к примеру с электронными письмами. Вы можете сохранять в базе данных дату отправки письма приглашённому человеку в соответствующем ряду таблицы. Если письмо уже отправлялось, то есть значение в базе данных отличается от NULL, повторно отправлять письмо не нужно. Очень простое решение. Также можно проверять, завершено ли оформление мероприятия. Если оформление не завершено, письмо отправлять не нужно.</p>
30 <p>Вернёмся к примеру с электронными письмами. Вы можете сохранять в базе данных дату отправки письма приглашённому человеку в соответствующем ряду таблицы. Если письмо уже отправлялось, то есть значение в базе данных отличается от NULL, повторно отправлять письмо не нужно. Очень простое решение. Также можно проверять, завершено ли оформление мероприятия. Если оформление не завершено, письмо отправлять не нужно.</p>
31 <p>Если обобщить:</p>
31 <p>Если обобщить:</p>
32 <ul><li>Убедитесь, что задача готова к исполнению. Если задача не готова к исполнению, ничего делать не надо.</li>
32 <ul><li>Убедитесь, что задача готова к исполнению. Если задача не готова к исполнению, ничего делать не надо.</li>
33 <li>Убедитесь, что задача ещё не исполнена. Если она исполнена, ничего не нужно делать.</li>
33 <li>Убедитесь, что задача ещё не исполнена. Если она исполнена, ничего не нужно делать.</li>
34 <li>Выполните задачу, занесите дату выполнения или другую информацию в базу данных. Запишите в лог, что задача выполнена. Например, укажите, что приглашение на такое-то мероприятие отправлено такому-то пользователю.</li>
34 <li>Выполните задачу, занесите дату выполнения или другую информацию в базу данных. Запишите в лог, что задача выполнена. Например, укажите, что приглашение на такое-то мероприятие отправлено такому-то пользователю.</li>
35 <li>Убедитесь, что задачи гранулированные. Нужно отправить пять писем? Запланируйте задачу, которая выполнит пять других задач. Пример кода ниже.</li>
35 <li>Убедитесь, что задачи гранулированные. Нужно отправить пять писем? Запланируйте задачу, которая выполнит пять других задач. Пример кода ниже.</li>
36 </ul><p>Увидели, что что-то идёт не так? Можно снова вызвать функцию, которая отправит все приглашения на все мероприятия. Снова проблемы, когда половина писем отправлена? Исправьте проблему и вызовите функцию снова. Достаточно просто выяснить, какие письма не были отправлены. В качестве бонуса: вы можете понять, сколько писем в день рассылается, когда шлётся больше всего писем и так далее.</p>
36 </ul><p>Увидели, что что-то идёт не так? Можно снова вызвать функцию, которая отправит все приглашения на все мероприятия. Снова проблемы, когда половина писем отправлена? Исправьте проблему и вызовите функцию снова. Достаточно просто выяснить, какие письма не были отправлены. В качестве бонуса: вы можете понять, сколько писем в день рассылается, когда шлётся больше всего писем и так далее.</p>
37 <p>Помните, некоторые очереди не гарантируют отправку одного письма один раз. К ним относится Amazon SQS. Поэтому ваши воркеры должны выполнять только идемпотентные задачи.</p>
37 <p>Помните, некоторые очереди не гарантируют отправку одного письма один раз. К ним относится Amazon SQS. Поэтому ваши воркеры должны выполнять только идемпотентные задачи.</p>
38 <h2>SQL-миграции</h2>
38 <h2>SQL-миграции</h2>
39 <p>Когда вы выполняете SQL-миграции, позаботьтесь об их идемпотентности.</p>
39 <p>Когда вы выполняете SQL-миграции, позаботьтесь об их идемпотентности.</p>
40 <p>Например, вам нужно разделить таблицу пользователя на две. Одна таблица будет для пользователей (<em>users</em>), а вторая для деталей, которые не всегда важны (<em>profiles</em>). Вы помещаете внешний ключ<em>user_id</em>в в таблицу<em>profiles</em>. Получаете миграцию, которая берёт каждый ряд в<em>users</em>(<em>SELECT * FROM users</em>) и вставляет ряд с пользовательскими данными в<em>profiles</em>. Запускаете миграцию, но<a>она крашится</a>через час. Это происходит из-за значений NULL, которые вы не учли. Вы исправляете ошибку, снова запускаете миграцию, но вдруг понимаете, что для некоторых пользователей созданы по два профиля.</p>
40 <p>Например, вам нужно разделить таблицу пользователя на две. Одна таблица будет для пользователей (<em>users</em>), а вторая для деталей, которые не всегда важны (<em>profiles</em>). Вы помещаете внешний ключ<em>user_id</em>в в таблицу<em>profiles</em>. Получаете миграцию, которая берёт каждый ряд в<em>users</em>(<em>SELECT * FROM users</em>) и вставляет ряд с пользовательскими данными в<em>profiles</em>. Запускаете миграцию, но<a>она крашится</a>через час. Это происходит из-за значений NULL, которые вы не учли. Вы исправляете ошибку, снова запускаете миграцию, но вдруг понимаете, что для некоторых пользователей созданы по два профиля.</p>
41 <p>Этого можно избежать с помощью идемпотентного решения. Вместо<em>SELECT * FROM users</em>можно выбрать пользователей, у которых ещё нет ряда в<em>profiles</em>. С таким решением миграцию можно запускать сколько угодно раз. В ходе каждого запуска будут обработаны данные тех пользователей, у которых ещё нет профиля. Огромное преимущество этого подхода - вам не нужно останавливать приложение при выполнении миграции. Как только вы готовы развернуть новую версию, в которой используется<em>profiles</em>, можно вызвать функцию, которая обеспечит миграцию новых пользователей. Это не самый лучший пример, так как пользователь во время миграции может изменить какие-то данные в таблице<em>users</em>. Учитывайте этот момент.</p>
41 <p>Этого можно избежать с помощью идемпотентного решения. Вместо<em>SELECT * FROM users</em>можно выбрать пользователей, у которых ещё нет ряда в<em>profiles</em>. С таким решением миграцию можно запускать сколько угодно раз. В ходе каждого запуска будут обработаны данные тех пользователей, у которых ещё нет профиля. Огромное преимущество этого подхода - вам не нужно останавливать приложение при выполнении миграции. Как только вы готовы развернуть новую версию, в которой используется<em>profiles</em>, можно вызвать функцию, которая обеспечит миграцию новых пользователей. Это не самый лучший пример, так как пользователь во время миграции может изменить какие-то данные в таблице<em>users</em>. Учитывайте этот момент.</p>
42 <h2>Денормализованные данные</h2>
42 <h2>Денормализованные данные</h2>
43 <p>У вас есть приложение, в котором у каждого пользователя есть много документов. Пользователи могут искать документы по тегам. Теги приходят из разных источников: названия документов, директорий, имена авторов, собственно теги и так далее. Вы решаете держать все теги для всех документов в таблице<em>tags</em>. Это выглядит так:</p>
43 <p>У вас есть приложение, в котором у каждого пользователя есть много документов. Пользователи могут искать документы по тегам. Теги приходят из разных источников: названия документов, директорий, имена авторов, собственно теги и так далее. Вы решаете держать все теги для всех документов в таблице<em>tags</em>. Это выглядит так:</p>
44 <ul><li><em>ID</em>(хэш)</li>
44 <ul><li><em>ID</em>(хэш)</li>
45 <li><em>Tag</em>(текст тега)</li>
45 <li><em>Tag</em>(текст тега)</li>
46 <li>_document<em>id</em>(внешний ключ к документу)</li>
46 <li>_document<em>id</em>(внешний ключ к документу)</li>
47 </ul><p>Когда вы добавляете автора в документ, в таблицу<em>tags</em>попадают данные. Когда вы удаляете автора, соответствующий тег удаляется из таблицы.</p>
47 </ul><p>Когда вы добавляете автора в документ, в таблицу<em>tags</em>попадают данные. Когда вы удаляете автора, соответствующий тег удаляется из таблицы.</p>
48 <p>Однажды вы замечаете в логах, что время от времени случались ошибки из-за проблемы кодировки символов. Вы фиксите проблему и разворачиваете приложение с обновлённым кодом. Однако видите, что много тегов отсутствует, и их нужно восстанавливать вручную.</p>
48 <p>Однажды вы замечаете в логах, что время от времени случались ошибки из-за проблемы кодировки символов. Вы фиксите проблему и разворачиваете приложение с обновлённым кодом. Однако видите, что много тегов отсутствует, и их нужно восстанавливать вручную.</p>
49 <p>Вместо функции<em>add_tag_for_new_author</em>вам нужно изначально сделать функцию<em>update_tags_for_document</em>. Эта функция не просто добавляет тег автора. Она проверяет документ, перестраивает список тегов и убеждается, что в базу данных попала корректная информация. При таком подходе таблица<em>tags</em>обрабатывается корректным способом: с помощью кэша. Вы можете удалить все ряды из таблицы и запустить<em>update_tags_for_document</em>. Требуется 2 секунды, чтобы обновить теги? Пусть этим занимается воркер, добавьте сообщение в очередь.</p>
49 <p>Вместо функции<em>add_tag_for_new_author</em>вам нужно изначально сделать функцию<em>update_tags_for_document</em>. Эта функция не просто добавляет тег автора. Она проверяет документ, перестраивает список тегов и убеждается, что в базу данных попала корректная информация. При таком подходе таблица<em>tags</em>обрабатывается корректным способом: с помощью кэша. Вы можете удалить все ряды из таблицы и запустить<em>update_tags_for_document</em>. Требуется 2 секунды, чтобы обновить теги? Пусть этим занимается воркер, добавьте сообщение в очередь.</p>
50 <h2>Как писать идемпотентные bash-скрипты</h2>
50 <h2>Как писать идемпотентные bash-скрипты</h2>
51 <p>Иногда случается такое: вы пишете bash-скрипт, запускаете его, но через какое-то время он завершается из-за ошибки. Вы фиксите ошибку в системе и снова запускаете скрипт. Но часть шагов вашего скрипта падает с ошибкой, так как эти шаги уже были выполнены при первом запуске. Чтобы создавать отказоустойчивые системы, нужно писать идемпотентные программы.</p>
51 <p>Иногда случается такое: вы пишете bash-скрипт, запускаете его, но через какое-то время он завершается из-за ошибки. Вы фиксите ошибку в системе и снова запускаете скрипт. Но часть шагов вашего скрипта падает с ошибкой, так как эти шаги уже были выполнены при первом запуске. Чтобы создавать отказоустойчивые системы, нужно писать идемпотентные программы.</p>
52 <h2>Bash-идиомы</h2>
52 <h2>Bash-идиомы</h2>
53 <p>Ниже вы найдёте несколько советов и bash-идиом, которые помогут писать идемпотентные скрипты. Вы наверняка используете некоторые из них, не задумываясь о побочных эффектах.</p>
53 <p>Ниже вы найдёте несколько советов и bash-идиом, которые помогут писать идемпотентные скрипты. Вы наверняка используете некоторые из них, не задумываясь о побочных эффектах.</p>
54 <h3>Создание пустого файла</h3>
54 <h3>Создание пустого файла</h3>
55 <p>Это простая задача. Команда<em>touch</em>по умолчанию идемпотентная. Вы можете вызывать её много раз без проблем. Повторный вызов не повлияет на контент файла. Но он изменит время модификации файла. Если вы его используете, будьте осторожны.</p>
55 <p>Это простая задача. Команда<em>touch</em>по умолчанию идемпотентная. Вы можете вызывать её много раз без проблем. Повторный вызов не повлияет на контент файла. Но он изменит время модификации файла. Если вы его используете, будьте осторожны.</p>
56 <h3>Создание директории</h3>
56 <h3>Создание директории</h3>
57 <p>Никогда не используйте команду<em>mkdir</em>как есть. Применяйте её с флагом<em>-p</em>. Этот флаг гарантирует отсутствие ошибки при запуске<em>mkdir</em>, если директория уже существует.</p>
57 <p>Никогда не используйте команду<em>mkdir</em>как есть. Применяйте её с флагом<em>-p</em>. Этот флаг гарантирует отсутствие ошибки при запуске<em>mkdir</em>, если директория уже существует.</p>
58 <h3>Создание символической ссылки</h3>
58 <h3>Создание символической ссылки</h3>
59 <p>Для создания символических ссылок используется такая команда:</p>
59 <p>Для создания символических ссылок используется такая команда:</p>
60 <p>Но эта команда генерирует ошибку, если вы повторно вызовите её с существующей целью. Чтобы сделать команду идемпотентной, добавьте флаг<em>-f</em>.</p>
60 <p>Но эта команда генерирует ошибку, если вы повторно вызовите её с существующей целью. Чтобы сделать команду идемпотентной, добавьте флаг<em>-f</em>.</p>
61 <p>Флаг<em>-f</em>удаляет целевой путь перед созданием символической ссылки, поэтому команда всегда будет успешной. Если вы ссылаетесь на директорию, нужно добавить флаг<em>-n</em>. В противном случае повторный вызов создаст символическую ссылку внутри директории.</p>
61 <p>Флаг<em>-f</em>удаляет целевой путь перед созданием символической ссылки, поэтому команда всегда будет успешной. Если вы ссылаетесь на директорию, нужно добавить флаг<em>-n</em>. В противном случае повторный вызов создаст символическую ссылку внутри директории.</p>
62 <p>Для безопасности всегда используйте такую команду:</p>
62 <p>Для безопасности всегда используйте такую команду:</p>
63 <h3>Удаление файла</h3>
63 <h3>Удаление файла</h3>
64 <p>Следующую ниже команду нежелательно использовать для удаления файлов:</p>
64 <p>Следующую ниже команду нежелательно использовать для удаления файлов:</p>
65 <p>Чтобы команда игнорировала несуществующие файлы, надо использовать флаг<em>-f</em>.</p>
65 <p>Чтобы команда игнорировала несуществующие файлы, надо использовать флаг<em>-f</em>.</p>
66 <h3>Изменение файла</h3>
66 <h3>Изменение файла</h3>
67 <p>Иногда необходимо добавить новую строку в файл, например,<em>/etc/fstab</em>. Нужно убедиться, что повторный запуск скрипта не приводит к добавлению строки ещё раз. Представьте, что используете такой скрипт:</p>
67 <p>Иногда необходимо добавить новую строку в файл, например,<em>/etc/fstab</em>. Нужно убедиться, что повторный запуск скрипта не приводит к добавлению строки ещё раз. Представьте, что используете такой скрипт:</p>
68 <p>Если вы запустите скрипт повторно, получите дублирующуюся запись в<em>/etc/fstab</em>. Один из способов сделать скрипт идемпотентным - проверить существование конкретных плейсхолдеров с помощью<em>grep</em>.</p>
68 <p>Если вы запустите скрипт повторно, получите дублирующуюся запись в<em>/etc/fstab</em>. Один из способов сделать скрипт идемпотентным - проверить существование конкретных плейсхолдеров с помощью<em>grep</em>.</p>
69 <p>В данном случае<em>-q</em>обозначает тихий режим, а<em>-F</em>- режим фиксированной строки.<em>Grep</em>в фоновом режиме завершится с ошибкой, если<em>/mnt/dev</em>не существует, поэтому<em>echo</em>не вызовется.</p>
69 <p>В данном случае<em>-q</em>обозначает тихий режим, а<em>-F</em>- режим фиксированной строки.<em>Grep</em>в фоновом режиме завершится с ошибкой, если<em>/mnt/dev</em>не существует, поэтому<em>echo</em>не вызовется.</p>
70 <blockquote><h3>Читайте также:</h3>
70 <blockquote><h3>Читайте также:</h3>
71 <p>Как использовать<a>коды завершения</a>в Bash-скриптах</p>
71 <p>Как использовать<a>коды завершения</a>в Bash-скриптах</p>
72 </blockquote><h3>Проверка существования переменной, файла или директории</h3>
72 </blockquote><h3>Проверка существования переменной, файла или директории</h3>
73 <p>Вы часто записываете в директорию, читаете из файла или выполняете простые строковые манипуляции с переменной. Например, у вас может быть инструмент, который создаёт новый файл, основываясь на определённых входящих данных.</p>
73 <p>Вы часто записываете в директорию, читаете из файла или выполняете простые строковые манипуляции с переменной. Например, у вас может быть инструмент, который создаёт новый файл, основываясь на определённых входящих данных.</p>
74 <p>Вычисление текста может быть дорогой операцией, поэтому вы не захотите писать его каждый раз, когда вызываете скрипт. Для идемпотентности вы проверяете существование файла с помощью флага<em>-f</em>встроенного свойства test командной оболочки.</p>
74 <p>Вычисление текста может быть дорогой операцией, поэтому вы не захотите писать его каждый раз, когда вызываете скрипт. Для идемпотентности вы проверяете существование файла с помощью флага<em>-f</em>встроенного свойства test командной оболочки.</p>
75 <p>В данном случае<em>-f</em>- только пример. Есть много других флагов, в том числе:</p>
75 <p>В данном случае<em>-f</em>- только пример. Есть много других флагов, в том числе:</p>
76 <ul><li>-d: директория;</li>
76 <ul><li>-d: директория;</li>
77 <li>-z: строка с нулевой длиной;</li>
77 <li>-z: строка с нулевой длиной;</li>
78 <li>-p: пайп;</li>
78 <li>-p: пайп;</li>
79 <li>-x: файл с разрешением на исполнение.</li>
79 <li>-x: файл с разрешением на исполнение.</li>
80 </ul><p>Например, если вы хотите установить бинарный файл, но только если его ещё не существует, можно использовать флаг<em>-x</em>таким способом:</p>
80 </ul><p>Например, если вы хотите установить бинарный файл, но только если его ещё не существует, можно использовать флаг<em>-x</em>таким способом:</p>
81 <p>Это устанавливает файл<em>op</em>в<em>/usr/local/bin</em>. Повторный запуск скрипта не приведёт к повторной установке бинарного файла. В данном случае есть ещё одно преимущество. Вы можете легко обновить бинарный файл. Для этого достаточно удалить его из системы, обновить<em>OP_VERSION</em><em>env</em>и повторно запустить скрипт. Список флагов и операторов можно получить с помощью<em>man test</em>.</p>
81 <p>Это устанавливает файл<em>op</em>в<em>/usr/local/bin</em>. Повторный запуск скрипта не приведёт к повторной установке бинарного файла. В данном случае есть ещё одно преимущество. Вы можете легко обновить бинарный файл. Для этого достаточно удалить его из системы, обновить<em>OP_VERSION</em><em>env</em>и повторно запустить скрипт. Список флагов и операторов можно получить с помощью<em>man test</em>.</p>
82 <h3>Форматирование устройства</h3>
82 <h3>Форматирование устройства</h3>
83 <p>Для форматирования можно использовать такую команду:</p>
83 <p>Для форматирования можно использовать такую команду:</p>
84 <p>Эта команда печатает атрибуты для заданного блока устройства. Соответственно, предварительное добавление значит продолжение форматирования только при ошибке<em>blkid</em>, которая сообщает, что соответствующий том ещё не отформатирован.</p>
84 <p>Эта команда печатает атрибуты для заданного блока устройства. Соответственно, предварительное добавление значит продолжение форматирования только при ошибке<em>blkid</em>, которая сообщает, что соответствующий том ещё не отформатирован.</p>
85 <h3>Монтирование устройства</h3>
85 <h3>Монтирование устройства</h3>
86 <p>Попытка монтировать том в существующий каталог может выполняться с помощью такой команды:</p>
86 <p>Попытка монтировать том в существующий каталог может выполняться с помощью такой команды:</p>
87 <p>Но если он уже установлен, возникнет ошибка. Можно проверять вывод команды<em>mount</em>. Но есть лучший вариант. Это использование команды<em>mountpoint</em>.</p>
87 <p>Но если он уже установлен, возникнет ошибка. Можно проверять вывод команды<em>mount</em>. Но есть лучший вариант. Это использование команды<em>mountpoint</em>.</p>
88 <p>Эта команда проверяет, является ли файл или каталог точкой монтирования. Флаг<em>-q</em>гарантирует, что она ничего не выводит и завершается в фоновом режиме. Если точка монтирования не существует, команда монтирует устройство.</p>
88 <p>Эта команда проверяет, является ли файл или каталог точкой монтирования. Флаг<em>-q</em>гарантирует, что она ничего не выводит и завершается в фоновом режиме. Если точка монтирования не существует, команда монтирует устройство.</p>
89 <h2>Завершение</h2>
89 <h2>Завершение</h2>
90 <p>Вы узнали о важности идемпотентности, а также познакомились со способами написания идемпотентных скриптов. Многие из предложенных советов и трюков давно известны, но разработчики часто пренебрегают этими возможностями. Некоторые из представленных идиом очень специфичные. К таким относятся монтирование и форматирование. Тем не менее создание идемпотентных программ - выигрышная стратегия в долгосрочной перспективе. Поэтому полезно знать даже специфичные идиомы.</p>
90 <p>Вы узнали о важности идемпотентности, а также познакомились со способами написания идемпотентных скриптов. Многие из предложенных советов и трюков давно известны, но разработчики часто пренебрегают этими возможностями. Некоторые из представленных идиом очень специфичные. К таким относятся монтирование и форматирование. Тем не менее создание идемпотентных программ - выигрышная стратегия в долгосрочной перспективе. Поэтому полезно знать даже специфичные идиомы.</p>
91 <p><em>Адаптированный перевод статей<a>The Importance of Idempotence</a>by Antoine Leclair и<a>How to write idempotent Bash scripts</a>by Fatih Arslan.</em></p>
91 <p><em>Адаптированный перевод статей<a>The Importance of Idempotence</a>by Antoine Leclair и<a>How to write idempotent Bash scripts</a>by Fatih Arslan.</em></p>