Разбор Crab Mysqldump2Git
Это старая версия документа.
Разбираем и правим до идеала, спрашиваем все, что непонятно
#!/bin/bash set -euEo pipefail echo "$0 $@ [$$] START" >&2 if [ "${1:---help}" = "--help" ]; then echo "Example: backup: $0 --user=root --password=mypass --host=localhost DB_NAME BACKUP_DIR" echo 'Example: restore: git log; git checkout;' echo 'Example: restore: for f in *.sql.*; do cat $f >>new.sql; done;' echo 'Example: restore: /usr/bin/mysql -uroot -p123 < new.sql' exit 0 fi USER="${1/--user=/}" PASSWORD="${2/--password=/}" DB_HOST="${3/--host=/}" DB_NAME="${4}" BACKUP_DIR="${5}" PREFIX="${DB_NAME}.sql" if [ ! -d .git ]; then git init . echo '*.sql' > .gitignore git add .gitignore git commit -m init fi mysql_dumpsplit(){ local table="" for table in "$PREFIX.table."*; do if [ $(stat -c %s "$table") -gt $((10*1024*1024)) ]; then split -a 4 -d -b 10M "$table" "$table.split." rm -f "$table"; fi done return 0 } cd "$BACKUP_DIR" rm -f "$PREFIX.table."* mysqldump --user="$USER" --host="$DB_HOST" --password="$PASSWORD" \ --opt -c --databases "$DB_NAME" \ --skip-quick \ --add-drop-table --add-drop-database --skip-extended-insert \ --create-options --single-transaction \ | csplit -n 4 -s -f"$PREFIX.table." - '/-- Table structure for table/' '{*}' mysql_dumpsplit git add . error=$( git commit -am $(date +%Y-%m-%d_%H-%M) 2>&1 ) \ || echo "$error" | grep -qm1 'nothing to commit' \ || { echo "$error"; exit 1; } git gc error=$( git push origin master 2>&1 )\ || echo "$error" | grep -qm1 'does not appear to be a git repositor' \ || { echo "$error"; exit 1; } echo "$0 $@ [$$] SUCCESS" >&2 exit 0
Разбор и комменты
#!/bin/bash ### Весь bash скрипт рассматриваем как инстанс объекта crab_mysqldump2git ### Глобальные переменные - это атрибуты(property) объекта ### функции - это методы объекта, либо иногда, это аналоги команд OS. ### если функция работает с переменной, которая не является атрибутом объекта, ### то она должна быть с входными параметрами ### падаем при любых необработанных ошибках set -euEo pipefail echo "$0 $@ [$$] START" >&2 if [ "${1:---help}" = "--help" ]; then echo "Example: backup: $0 --user=root --password=mypass --host=localhost DB_NAME BACKUP_DIR" echo 'Example: restore: git log; git checkout;' echo 'Example: restore: for f in *.sql.*; do cat $f >>new.sql; done;' echo 'Example: restore: /usr/bin/mysql -uroot -p123 < new.sql' exit 0 fi ### Подготовительная работа ### Подготавливаем глобальные переменные, что тождественно приватным property объекта self ### по сути понимаем, что $USER - это self.user или crab_mysqldump2git.user ### парсинг аргументов не стандартизован, для простоты кода оставим позиционно зависимый USER="${1/--user=/}" PASSWORD="${2/--password=/}" DB_HOST="${3/--host=/}" DB_NAME="${4}" BACKUP_DIR="${5}" PREFIX="${DB_NAME}.sql" ### подготовительная работа инициализация каталога при необходимости ### можно вынести в метод git_check_and_init() if [ ! -d .git ]; then git init . echo '*.sql' > .gitignore git add .gitignore git commit -m init fi ### Описание пользовательских команд(методов) и функций ### метод может работать с глобальными переменным без входящих значений ### если эти переменные это атрибут объекта self-script или его CONF_NAME переменные ### но если это иные переменные, то обязательно делать через входные значения ### например Только аргументом если мы обрабатываем файлы, ### через for file in /tmp/mysql/*; do funct_name "$file"; done mysql_dumpsplit(){ local table="" for table in "$PREFIX.table."*; do if [ $(stat -c %s "$table") -gt $((10*1024*1024)) ]; then split -a 4 -d -b 10M "$table" "$table.split." rm -f "$table"; fi done ### return 0 всегда обязательно, иначе вызов функции может упасть тк вернется не ноль, ### а код возврата от последней команды даже если мы его обработали и он не приведет к локальному падению return 0 } ### основной алгоритм скрипта, это последовательность команд и методов ### простейшие команды не выносим в отдельные методы ### КОМАНДА №1 cd "$BACKUP_DIR" ### КОМАНДА №2 всегда стараемся избежать rm -rf rm -f "$PREFIX.table."* ### КОМАНДА №3 mysqldump --user="$USER" --host="$DB_HOST" --password="$PASSWORD" \ --opt -c --databases "$DB_NAME" \ --skip-quick \ --add-drop-table --add-drop-database --skip-extended-insert \ --create-options --single-transaction \ | csplit -n 4 -s -f"$PREFIX.table." - '/-- Table structure for table/' '{*}' ### КОМАНДА №4 - это команда-метод он использует глобальные переменные, которые по сути атрибуты объекта и это нормально mysql_dumpsplit ### КОМАНДА №5 git add . ### КОМАНДА №6 пример обработки команды у которой бывает код возврата не ноль, ### но при этом это не ошибка std_err='nothing to commit' ### это разрешенный и достаточно понятный сахар, ### мы разрешаем такой сахар, хоть он и нарушает правило, что нельзя условие делать без && ### другие вариации этой конструкции тоже разрешены error=$( git commit -am $(date +%Y-%m-%d_%H-%M) 2>&1 ) \ || [[ "$error" == *'nothing to commit'* ]] || { echo "$error"; exit 1; } ### КОМАНДА №7 git gc ### КОМАНДА №8 error=$( git push origin master 2>&1 )\ || echo "$error" | grep -qm1 'does not appear to be a git repositor' \ || { echo "$error"; exit 1; } echo "$0 $@ [$$] SUCCESS" >&2 ### обязательно ставим exit 0, ### Иначе не гарантии кода возврата 0 даже если скрип выполнился успешно ### тк скрипт может вернуть НЕ 0, а например последний резалт от grep и тп ### даже если мы его отловили ### при этом не нужно думать, что скрипт всегда вернет 0, ### скрипт вернет 0 только если все команды успешно выполнилось. exit 0
правила разработки как надо делать разбор crab mysqldump2git |
Yes(31) No(0) Clear Yes: No: |
Обсуждение
Сугубо моя точка зрения:
Первое: соотношение размера скрипта с приносимой пользой, 8 команд - 100 строк (хотя там комменты).
Второе: переносы длинных строк не делают строку короче, концептуально это всё та же длинная строка. Лучше заменить на более читаемое (практически как английский язык):
Или:
Для более удобного управления потоком исполнения я бы вынес даже в функцию это, в итоге будет отделена логика скрипта от деталей реализации и обработок ошибок:
Третье: из-за размера теряется суть - что это вообще за скрипт и зачем он нужен и что делает.
Можно прояснить это с помощью оглавления-
main()
:Четвёртое: моя вкусовщина - команды-методы зло по причине того, что их менее удобно отлаживать по кирпичикам. Вставить в консоль код и выполнить:
проще, чем вставить в консоль код, выполнить:
подумать "тысяча чертей, забыл из начала скрипта вставить переменные", вставить их, запустить заново, заметить что из-за пустого prefix потенциально что-то стёр, т.к. set -eu в консоль вставить забыл. Ну, с аргументом та же проблема, однако.
В общем:
Насчёт exit 0 - я плохо понимаю зачем делать exit 0 если я хочу возвращать код возврата последней команды, я не хочу его обрабатывать, пусть оно падает и рушит вместе с собой остальной скрипт.
Это скрипт для разбора, поэтому оч.много комментов
Задача не короче, а читабельней с учетом особенностей мозга человека.
Это был пример проверенного, понятного, разрешенного Сахара для короткой записи, не нравится не юзай)
По сравнению с предложенным Сахаром сильное переусложнение, и имеет смысл скорей при многократном использовании.
>Третье: из-за размера теряется суть - что это вообще за скрипт и зачем он нужен и что делает.
Я всегда тремя руками за такой подход. Fixed.
1. mysql_dumpsplit без импорта все равно не будет такой команды.
2. Передавать каждый раз глобальный проперти в метод это не удобно, тут надо понять объектный подход в скрипте или процедурный. Если объектный тот не передавать и рассматривать глобальные переменные как свойства объекта self-script. Если процедурный, то передавать. Смешивать не желательно, без необходимости. Лично я за объектный подход и этот скрипт пример этого подхода.
Писать такой скрипт на питоне нерационально и дольше в разы, авторазбор параметров есть в в либе carbon.sys, но здесь его не стал использовать, что было явнее.
В целом согласен. Fixed.
1. Скрипт и так упадет по set -e и даст код возврата команды падения.
2. exit 0 обязателен потому что, если в конце будет условие или выход из цикла и забудешь написать exit 0, то вернется не ноль и все упадет.
Всегда надо делать exit 0 и return 0 и не думать каждый раз - это позволит избежать кучу проблем и освобождает мозг.
Понял почему мне не нравится объектный подход в bash-скриптах:
Олег, в старых правилах все так и было как ты описываешь, хочешь процедурно - делай никто не запрещает.
Ноль один и бесконечность, у 99% скриптов нет развития на подскрипты при продуманной архитектуре.
Менять только в одном месте убрать тело функции, сделать там вызов внешний проги да и все делов то.
Потому что это удобно, я же пытаюсь объяснить когда глобальные переменные это не зло, тк они не глобальные переменные, а тождественны проперти.
set -euEo pipefail Вроде как так уже нельзя писать.
Fixed
set -euE
тк можно сделать
fixed