2017-10-17 Atomic Bash.md
Различия
Здесь показаны различия между двумя версиями данной страницы.
корзина:статьи_участников:2017-10-17_atomic_bash.md [04.09.2018 07:15] 127.0.0.1 внешнее изменение |
корзина:статьи_участников:2017-10-17_atomic_bash.md [20.05.2019 15:18] |
||
---|---|---|---|
Строка 1: | Строка 1: | ||
- | # Атомарность в bash | ||
- | Tags: bash, правила программирования | ||
- | |||
- | В программировании большой частью является работа с исключительными ситуациями, ошибками и прочими внезапными вещами, кооторые отвлекают тебя от непосредственной логии приложения. | ||
- | Ситуация ухудшается, когда ты начинаешь пользоваться bash, часто люди вообще забивают на надежность и скрипты могут падать в очень странной конфигурации. | ||
- | Причем, по-умолчанию, bash продолжит выполнять твой скрипт, даже если одна из команд упала или когда ты написал переменную с ошибкой | ||
- | ```bash | ||
- | data_dir='/tmp/test_data' | ||
- | rm -rf "${vata_dir}/" | ||
- | ``` | ||
- | Конечно, первой рекомендацией при начале написания скрипта, является включение всех необходимых флагов: | ||
- | |||
- | ```bash | ||
- | set -euEo pipefail | ||
- | ``` | ||
- | |||
- | Важно заметить, что стиль кода при этом серьезно меняется, часто если скрипт был написан без этих опций, включение этих опций потребует значительной переработки кода, мы часто код вообще переписываем. | ||
- | Имейте в виду, что set -e пропадает внутри функций, вызываемых из условных выражений и вернуть его внутри просто не получится. | ||
- | |||
- | ```bash | ||
- | set -euEo pipefail | ||
- | f(){ | ||
- | set -e # Не сработает | ||
- | false | ||
- | echo "Hello!" | ||
- | return 0 | ||
- | } | ||
- | if ! f; then | ||
- | echo "Newer happen" | ||
- | fi | ||
- | ``` | ||
- | |||
- | Здесь вы заметите return 0 в конце функции. Мы ввели правило, что в конце любой функции должно находиться return 0, это спасает от срабатываний set -e, когда это не нужно: | ||
- | |||
- | ```bash | ||
- | set -euEo pipefail | ||
- | f(){ | ||
- | echo "Do somphing..." | ||
- | [ -f /tmp/test.$$ ] && rm -f /tmp/test.$$ | ||
- | } | ||
- | echo "Hello!" | ||
- | f | ||
- | echo "Newer happen!" | ||
- | ``` | ||
- | |||
- | Раньше мы условное выражение расписывали в виде if then fi, пока не додумались, что return 0 избавит нас от этого | ||
- | |||
- | ```bash | ||
- | set -euEo pipefail | ||
- | f(){ | ||
- | echo "Do somphing..." | ||
- | [ -f /tmp/test.$$ ] && rm -f /tmp/test.$$ | ||
- | return 0 | ||
- | } | ||
- | echo "Hello!" | ||
- | f | ||
- | echo "Goodbye!" | ||
- | ``` | ||
- | |||
- | Но вернемся к первоначальной проблеме: скриптов все больше, багов тоже, как жить/что делать? | ||
- | Возьмем среднестатистический пример: нам нужно распарсить один файл и записать результат в соседний. | ||
- | |||
- | ```bash | ||
- | cat /home/one_file | sed 's/old_stuff/new_stuff/' > /tmp/tmp_file | ||
- | echo "Do somphing..." | ||
- | cat /tmp/tmp_file > /home/second_file | ||
- | ``` | ||
- | |||
- | Однажды у вас файл стал в 2 раза больше, через пол года файл вообще опустел. Самое забавное, что в этом начинают винить нагрузку на диск, внезапную многопоточность, солнечные бури... | ||
- | В принципе, можно считать что скрипт свою задачу таки выполняет и это правда. Скрипт уходит в продакшен, программист со временем теряет уверенность в работе всей системы, тихо материт гребаный баш, вот бы на питоне... | ||
- | Но давайте разбираться, скрипт не защищен от одновременного использования временными файлами, исправляем: | ||
- | |||
- | ```bash | ||
- | trap _exit EXIT | ||
- | _exit(){ | ||
- | res=$? | ||
- | rm -f /tmp/test_script*.$$ | ||
- | return $res | ||
- | } | ||
- | cat /home/one_file | sed 's/old_stuff/new_stuff/' > /tmp/test_script.tmp_file.$$ | ||
- | echo "Do somphing..." | ||
- | cat /tmp/test_script.tmp_file.$$ > /home/second_file | ||
- | ``` | ||
- | |||
- | Все временные файлы мы помечаем PIDом текущего процесса, плюс ловим выход из скрипта и подчищаем за собой все временные файлы, независимо от того, выполнился скрипт до конца или нет. | ||
- | Удаление всех файлов по маске с именем скрипта и пидом позволит не задумываться о «сборке мусора» и захламлению каталога /tmp кучей мелких файлов, хотя у нас в последнее время пропагандируется отказ от этого, но я пока не буду это рассматривать. | ||
- | Но что делать, если скрипт упадет в середине записи в файл (например, от банальной нехватки места)? Сохранять результат во временный файл и когда он полностью готов, заменять новой версией старый файл атомарно, одной командой mv -f: | ||
- | |||
- | ```bash | ||
- | cat /home/one_file | sed 's/old_stuff/new_stuff/' > /tmp/test_script.tmp_file.$$ | ||
- | echo "Do somphing..." | ||
- | mv -f /tmp/test_script.tmp_file.$$ /home/second_file | ||
- | ``` | ||
- | |||
- | В этом случае, даже если параллельно запустить несколько копий этого скрипта, файл /home/second_file всегда будет в корректном состоянии и к нему можно обращаться за данными в любой момент. | ||
- | Придерживаясь всех этих советов, вы всегда будете иметь актуальные и корректные версии файлов, без их резервных копий и блокировок от повторного запуска. | ||
- | |||
- | |||
- | ~~OWNERAPPROVE~~ | ||