Strongbash
Различия
Здесь показаны различия между двумя версиями данной страницы.
Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
соглашения_кода:strongbash [08.04.2018 17:30] admin ↷ Имя страницы соглашения_кода:strongbash изменено на соглашения_кода:strongbash_коменты |
соглашения_кода:strongbash [14.01.2020 09:26] nikolay_carbonsoft |
||
---|---|---|---|
Строка 1: | Строка 1: | ||
+ | ===== Соглашение по строгому bash ===== | ||
+ | TODO_OSV возможно выборочно проверять номера из https://github.com/koalaman/shellcheck | ||
+ | |||
+ | ===== strongbash001 ===== | ||
+ | **Обязательно должен быть выполнен crab_indent**. | ||
+ | <hidden Почему...> | ||
+ | - indent делает код более читаемым | ||
+ | - использование indent позволяет обнаруживать ошибки | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | Конструкция **if-then** располагаются в одной строке.\\ | ||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | if [ "$EUID" -ne "0" ]; then | ||
+ | echo "Please run as root" | ||
+ | fi | ||
+ | </code> | ||
+ | |||
+ | |||
+ | Фигурная скобка **"{"** для определния тела функции должна быть в одной строке с функцией.\\ | ||
+ | После переноса строки добавляем **<Tab>**.\\ | ||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | myfunc1() { | ||
+ | commands... | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Объявление функции без ключевого слова **function**.\\ | ||
+ | Ключевое слово function было введено в ksh.\\ | ||
+ | POSIX стандартизирует только синтаксис funcname().\\ | ||
+ | Код в скобках с отступами.\\ | ||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | myfunc1() { | ||
+ | echo "Hello world" | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | <color #ed1c24>Плохо:</color> | ||
+ | <code bash> | ||
+ | function myfunc1 { | ||
+ | echo "Hello world" | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Инструкция **case ** без оступов:\\ | ||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | case "$variable" in | ||
+ | "$condition1" ) | ||
+ | command... | ||
+ | ;; | ||
+ | |||
+ | "$condition2" ) | ||
+ | command... | ||
+ | ;; | ||
+ | |||
+ | esac | ||
+ | </code>\\ | ||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | #!/bin/bash | ||
+ | |||
+ | set -eu | ||
+ | |||
+ | func1(){ | ||
+ | grep 'zzz' /tmp/temp1.tmp | cat \ | ||
+ | | grep zza \ | ||
+ | && echo zzz || echo aaa | ||
+ | return 0 | ||
+ | } | ||
+ | |||
+ | if func1; then | ||
+ | echo true | ||
+ | else | ||
+ | echo false | ||
+ | fi | ||
+ | |||
+ | ( | ||
+ | cd /tmp/ | ||
+ | ls | ||
+ | ) \ | ||
+ | | ( | ||
+ | while read t; do | ||
+ | echo $t | ||
+ | done | ||
+ | ) | ||
+ | |||
+ | case "${2:-bash}" in | ||
+ | bash) | ||
+ | echo bash | ||
+ | ;; | ||
+ | *) | ||
+ | echo "not supported yet. plz add new style" | ||
+ | ;; | ||
+ | esac | ||
+ | |||
+ | exit 0 | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== strongbash002 ===== | ||
+ | **Должен быть установлен set -eu или include crab_sys.sh** \\ | ||
+ | **Категорически нельзя ставить <del>set -o pipefail</del> на весь файл см strongbash034_6** | ||
+ | <WRAP center round important>ВНИМАНИЕ set -eu это самое главное правило, оно дает режим strong.\\ | ||
+ | Позволяет отлавливать и исключать неявное и непредсказуемое поведение программы, а также быстрей отлаживать.\\ | ||
+ | </WRAP> | ||
+ | <WRAP center round important> | ||
+ | ОЧЕНЬ БОЛЬШОЕ ВНИМАНИЕ set -eu используется для того, чтобы отловить неизвестные **будущие** ошибки программы и не дать ей выполняться далее.\\ | ||
+ | set -eu **не должен** использоваться для реализации какой либо логики основного алгоритма, другими словами программа с set -eu и без set -eu должна работать одинаково в основной логике.\\ | ||
+ | </WRAP> | ||
+ | <hidden Что включает...> | ||
+ | set -e падать если есть необработанные ошибки, а точнее необработанный код возврата.\\ | ||
+ | set -u падать если используется неинициализированная переменная.\\ | ||
+ | set -o pipefail падать в ERR если в одной из пайп команд произошла ошибка, но это приводит к ошибкам при работе grep -q grep -m1 head -n1 tail и подобные команды посылают sigpipe в левую половину, что приводит падению левой части pipe и нарушению логики скрипта. Менять привычные конструкции нерационально, поэтому set -o pipefail нужно использовать только в отдельных участках программы и выключать после использования set +o pipefail.\\ | ||
+ | set -E наследовать trap ERR в функциях, что важно для debug.\\ | ||
+ | </hidden> | ||
+ | <hidden Как не надо делать...> | ||
+ | <color #ed1c24>Плохо делать ассерты вида:</color>\\ | ||
+ | <code bash> | ||
+ | [ $# = 3 ] | ||
+ | </code> | ||
+ | <color #22b14c>Хорошо явно отлавливать и писать текстом пользователю о **заранее** известных ошибках:</color>\\ | ||
+ | <code bash> | ||
+ | [ $# != 3 ] && { echo 'Укажите 3 параметра'; exit 1; } | ||
+ | </code> | ||
+ | <color #ed1c24>Плохо **пытаться** использовать возможности set -e для формирования кода возврата функций:</color> | ||
+ | <code bash> | ||
+ | set -e | ||
+ | func1(){ | ||
+ | git log > /tmp/myqqq.txt | ||
+ | grep qqq /tmp/myqqq.txt | ||
+ | # ^^^ не надо надеяться на то, что здесь упадет и функция вернет не ноль, здесь не остановится выполнение функции. | ||
+ | # такой подход технически невозможен ни с ни без set -e | ||
+ | return 0 | ||
+ | } | ||
+ | ### это работать не будет | ||
+ | func1 && echo "qqq найден" | ||
+ | </code> | ||
+ | |||
+ | <color #ed1c24>Плохо использовать стандартный кода возврата функций тк это отключает set -e:</color> | ||
+ | <code bash> | ||
+ | set -e | ||
+ | func1(){ | ||
+ | set -e ### здесь писать бесполезно опция работать не будет тк мы берем код возврата функции | ||
+ | git log > /tmp/myqqq.txt ### если здесь ошибка, то мы ее не поймаем и не упадем тк set -e отключится | ||
+ | grep qqq /tmp/myqqq.txt && return 0 || return $? | ||
+ | } | ||
+ | ### это будет работать, но потенциальные ошибки функции не приведут к падению скрипта и код становится менее надежным | ||
+ | func1 && echo "qqq найден" | ||
+ | </code> | ||
+ | |||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | func1(){ | ||
+ | set -e ### требуется указать еще раз чтоб работало ret=$( func1 ) | ||
+ | git log > /tmp/myqqq.txt ### если здесь ошибка, то мы упадем ура | ||
+ | grep qqq /tmp/myqqq.txt && echo TRUE || echo FALSE ### следует понимать, что ошибки grep не уронят скрипт, а приведут к echo FALSE | ||
+ | return 0 | ||
+ | } | ||
+ | ret="$(func1)" | ||
+ | [ "$ret" = "TRUE" ] && echo "qqq найден" | ||
+ | </code> | ||
+ | <color #22b14c>Для паранои:</color> | ||
+ | <code bash> | ||
+ | func1(){ | ||
+ | set -e ### требуется указать еще раз чтоб работало ret=$( func1 ) | ||
+ | git log > /tmp/myqqq.txt ### если здесь ошибка, то мы упадем | ||
+ | ret="$(grep qqq /tmp/myqqq.txt)" ### этим мы отловим ошибки grep b и упадем | ||
+ | [ -n "$ret" ] && echo TRUE || echo FALSE | ||
+ | return 0 | ||
+ | } | ||
+ | ret="$(func1)" | ||
+ | [ "$ret" = "TRUE" ] && echo "qqq найден" | ||
+ | </code> | ||
+ | |||
+ | </hidden> | ||
+ | <hidden Проблемы и решения set -e в условиях и функциях> | ||
+ | <color #ed1c24>Проблема, если мы вызываем функцию и анализируем ее код возврата\\ | ||
+ | в ней перестает работать set -e :</color>\\ | ||
+ | <code bash> | ||
+ | set -e | ||
+ | check_is_oversized(){ | ||
+ | set -e ### здесь бесполезно добавлять тк берется код возврата функции | ||
+ | size="$( stat -c %s $1 )" | ||
+ | [ "$size" -gt 1000 ] && return 0 # если программа stat упадет? то ошибки не будет тк set -e здесь выключен уже | ||
+ | # в итоге вернется return 1 даже если stat упал | ||
+ | return 1 | ||
+ | } | ||
+ | check_is_oversized /tmp34/11.log && echo "файл слишком большой" ### команда '&&' отключит обработку ошибок функции check_is_oversized | ||
+ | </code> | ||
+ | <color #22b14c>Решение, можно использовать echo TRUE вместо return 0 :</color>\\ | ||
+ | <code bash> | ||
+ | set -e | ||
+ | check_is_oversized(){ | ||
+ | set -e ### обязательно еще раз тк он снимется при ret=$( check_is_oversized ) | ||
+ | size="$( stat -c %s $1 )" ### если файла нет мы упадем ура | ||
+ | [ "$size" -gt 1000 ] && echo TRUE || echo FALSE | ||
+ | return 0 | ||
+ | } | ||
+ | |||
+ | ### [ "$( check_is_oversized /tmp34/11.log )" = TRUE ] --- так нельзя тк любое условие полностью выключает set -e | ||
+ | |||
+ | ret=$( check_is_oversized /tmp34/11.log ) ### обязательно через промежуточную переменную иначе тоже set -e потеряется | ||
+ | [ "$ret" = TRUE ] && echo "файл слишком большой" | ||
+ | |||
+ | ### Из минусов такого подхода это запуск сабшела и небольшой оверхед, | ||
+ | ### поэтому при массовых(10000) вызовах функции приходится ее использовать с потерей контроля ошибок и обычным return | ||
+ | ### или можно использовать глобальную переменную ERRNO= или глобальную FNAME_RET= | ||
+ | ### или работать процедурно через eval: funct_search txt1 txt2 rettxt1 funct_search () { eval "$3"='$value' } | ||
+ | </code>\\ | ||
+ | <color #ed1c24>Проблема set -e не работает в условиях:</color>\\ | ||
+ | <code bash> | ||
+ | # если команда ip упадет мы об этом не узнаем( хотя иногда это может быть и неважно для логики). | ||
+ | if ip r g 1 | grep 192.168.1.1; then | ||
+ | echo "Шлюз указан верно" | ||
+ | fi | ||
+ | </code> | ||
+ | <color #22b14c>Решение, можно использовать промежуточную переменную:</color> | ||
+ | <code bash> | ||
+ | ip_ret="$( ip r g 1 )" | ||
+ | if echo "$ip_ret" | grep 192.168.1.1; then | ||
+ | echo "Шлюз указан верно" | ||
+ | fi | ||
+ | </code> | ||
+ | <color #22b14c>Решение2, можно использовать промежуточную переменную:</color> | ||
+ | <code bash> | ||
+ | ip_ret="$( ip r g 1 )" || true | ||
+ | if [ -z "$ip_ret" ] then | ||
+ | echo "Не можем получить шлюз" | ||
+ | fi | ||
+ | </code> | ||
+ | </hidden> | ||
+ | <hidden Непривычность set -u > | ||
+ | <color #ed1c24>Проблема set -u иногда непривычен для входящих параметров:</color>\\ | ||
+ | <code bash> | ||
+ | src=$1 # если $1 не передан, то программа упадет | ||
+ | </code> | ||
+ | <color #22b14c>Решение, можно использовать default значения переменных:</color> | ||
+ | <code bash> | ||
+ | echo ${1:-defaulttxt} | ||
+ | </code> | ||
+ | <code bash> | ||
+ | [ "${1:---help}" = '--help' ] && { echo 'Example: $0 src dst'; exit 0; } | ||
+ | </code> | ||
+ | Или учитывая правило skill -1 сделать так:\\ | ||
+ | <code bash> | ||
+ | [ "${1:-}" = '' -o "${1:-}" = '--help' ] && { echo 'Example: $0 src dst'; exit 0; } | ||
+ | или | ||
+ | my_src="${1:-}" | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ===== strongbash003 ===== | ||
+ | |||
+ | **Нельзя использовать || true в условных выражениях.**\\ | ||
+ | |||
+ | <hidden Почему...>Это миф что без них не работает и все падает при set -e.\\ | ||
+ | Все отлично работает, если return 0 в конце каждой функции и exit 0 в конце каждого файла.</hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #ed1c24>Как не надо делать:</color> | ||
+ | <code bash> | ||
+ | funct1(){ | ||
+ | [ a = b ] && echo Привет || true | ||
+ | } | ||
+ | </code> | ||
+ | <color #22b14c>Как надо делать:</color> | ||
+ | <code bash> | ||
+ | funct1(){ | ||
+ | [ a = b ] && echo Привет | ||
+ | return 0 | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ===== strongbash004 ===== | ||
+ | |||
+ | **Нельзя использовать else true в условных выражениях**\\ | ||
+ | см strongbash003 | ||
+ | |||
+ | ===== strongbash005 ===== | ||
+ | **В конце каждой функции должен быть return 0 или return $ret или exit 0 или exit $ret**\\ | ||
+ | <hidden Почему...>Это обязательно, чтобы не передать случайно неявное и нежелаемое значение последнего if, вместо явного $?.</hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #ed1c24>Плохо:</color> | ||
+ | <code bash> | ||
+ | prepare_text(){ | ||
+ | local text="${1:-}" | ||
+ | [ "$text" = "" ] && echo "Привет" | ||
+ | ### "вот здесь мы отловили код возврата и не должны упасть, но мы упадем сразу после выхода из функции" | ||
+ | ### тк вернется последний код возврата | ||
+ | }; | ||
+ | arg1=$( prepare_text "555" ) | ||
+ | echo $arg1 | ||
+ | </code> | ||
+ | |||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | prepare_text(){ | ||
+ | local text="${1:-}" | ||
+ | [ "$text" = "" ] && echo "Привет" | ||
+ | return 0 ### мы всегда явно задаем, что мы хотим вернуть либо return 0 либо return $ret | ||
+ | }; | ||
+ | arg1=$( prepare_text "555" ) | ||
+ | echo $arg1 | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ===== strongbash006 ===== | ||
+ | **В начале каждого файла должен быть echo START и USAGE, INFO, EXAMPLE\\ | ||
+ | В конце каждого файла должны быть echo SUCCESS и exit 0 :\\ ** | ||
+ | Для продвинутых разработчиков лучше использовать в начале скрипт: include crab_sys.sh | ||
+ | <hidden Почему...> | ||
+ | - exit 0 для того чтобы избежать случайный код возврата. | ||
+ | - USAGE, INFO, EXAMPLE использования важен, чтобы программа могла жить в будущем и не была выброшена. | ||
+ | - echo START echo SUCCESS делает удобным использование и чтение консоли и логов. | ||
+ | - include crab_sys.sh позволит автоматически деалать echo START echo SUCCESS echo FAIL sys::usage "$@" # --help и показывает callstack при падении | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | #!/bin/bash | ||
+ | echo "$0 $@ [$$] START" >&2 | ||
+ | if [ "${1:---help}" = '--help' ]; then | ||
+ | echo "Usage: $0 src dst" | ||
+ | echo "Example: $0 /tmp/12 /tmp/14.gz" | ||
+ | echo "Info: Копирование со сжатием" | ||
+ | exit 0 | ||
+ | fi | ||
+ | cmd1 | ||
+ | cmd2 | ||
+ | cmd3 | ||
+ | echo "$0 $@ [$$] SUCCESS" >&2 | ||
+ | exit 0 | ||
+ | </code> | ||
+ | <color #22b14c>Хорошо сделать функцию</color> <nowiki> | ||
+ | __exit()</nowiki><color #22b14c>, если много exit 0 в программе</color> | ||
+ | <code bash> | ||
+ | #!/bin/bash | ||
+ | echo "$0 $@ [$$] START" >&2 | ||
+ | [ "${1:---help}" = '--help' ] && { echo Example: $0 src dst; exit 0; } | ||
+ | ARGV="$@" | ||
+ | __exit(){ | ||
+ | local ret=$1 | ||
+ | [ "$ret" = 0 ] && echo "$0 $ARGV [$$] SUCCESS" >&2 \ | ||
+ | || echo "$0 $ARGV [$$] FAILED" >&2 | ||
+ | exit $ret | ||
+ | } | ||
+ | |||
+ | cmd1 | ||
+ | cmd2 || __exit 0 | ||
+ | cmd3 | ||
+ | echo "$0 $@ [$$] SUCCESS" >&2 | ||
+ | exit 0 | ||
+ | </code> | ||
+ | |||
+ | <color #22b14c>Хорошо для продвинутых и для карбон софт:</color> | ||
+ | <code bash> | ||
+ | #!/bin/bash | ||
+ | source crab_sys.sh | ||
+ | sys::usage "$@" | ||
+ | ### --help Info: Автоматически создавать и коммитить все файлы в каталоге | ||
+ | ### --help Usage: auto_git.sh [install] /var/www/html | ||
+ | ### --help Example: auto_git.sh /var/www/html | ||
+ | ### Пример sys::arg_parse "txt1" "txt2" "--named1=a1" "--named2=a2" "--forced" | ||
+ | ### Пример echo "${ARGV[1]}==txt1 ${ARGV[2]}==txt2 ${ARG_NAMED1}==a1 ${ARGV_NAMED2}==a2 ${ARG_FORCED}==TRUE" | ||
+ | sys::arg_parse "$@" | ||
+ | cmd1 | ||
+ | cmd2 | ||
+ | cmd3 | ||
+ | exit 0 | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | <hidden Решение проблем...> | ||
+ | <color #22b14c>Хорошо для тихих файлов:</color> | ||
+ | <code bash> | ||
+ | # echo "$0 START">&2 | ||
+ | ### --help Info: Автоматически создавать и коммитить все файлы в каталоге | ||
+ | ### --help Usage: auto_git.sh [install] /var/www/html | ||
+ | ### --help Example: auto_git.sh /var/www/html | ||
+ | # echo "$0 SUCCESS">&2 | ||
+ | exit 0 | ||
+ | </code> | ||
+ | |||
+ | |||
+ | <color #22b14c>Или для тихих файлов с crab_sys.sh:</color> | ||
+ | <code bash> | ||
+ | __SILENT=TRUE | ||
+ | source crab_sys.sh | ||
+ | ### --help Info: Автоматически создавать и коммитить все файлы в каталоге | ||
+ | ### --help Usage: auto_git.sh [install] /var/www/html | ||
+ | ### --help Example: auto_git.sh /var/www/html | ||
+ | exit 0 | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== strongbash007 ===== | ||
+ | |||
+ | **В конце каждого файла должен быть exit 0 или для библиотек: # exit 0.** | ||
+ | <hidden Почему...> | ||
+ | Мы должны явно задавать все легальные выходы из программы аналогично как с функциями.\\ | ||
+ | exit 0 в отличии от exit $ret дополнительно защитит от будущих ошибок при рефакторинге кода.\\ | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #22b14c>Хорошо</color> | ||
+ | <code bash> | ||
+ | #!/bin/bash | ||
+ | echo "$0 $@ [$$] START" >&2 | ||
+ | cmd1 | ||
+ | [ $sz -gt 110 ] && { echo "Ошибка sz=$sz"; exit 2; } | ||
+ | cmd2 | ||
+ | cmd3 | ||
+ | echo "$0 $@ [$$] SUCCESS" >&2 | ||
+ | exit 0 | ||
+ | </code> | ||
+ | <color #ed1c24>Плохо exit $ret тк в будущем при рефакторинге может появиться неоднозначности</color> | ||
+ | <code bash> | ||
+ | #!/bin/bash | ||
+ | echo "$0 $@ [$$] START" >&2 | ||
+ | cmd1 | ||
+ | [ $sz -gt 110 ] && { echo "Ошибка sz=$sz"; exit 2; } | ||
+ | cmd2 | ||
+ | cmd3 || ret=$? | ||
+ | echo "$0 $@ [$$] SUCCESS" >&2 | ||
+ | exit $ret | ||
+ | </code> | ||
+ | |||
+ | <color #22b14c>Хорошо. Если Ваш файл должен возвращать разные коды легально, то нужно это сделать явно : | ||
+ | </color> | ||
+ | <code bash> | ||
+ | #!/bin/bash | ||
+ | echo "$0 $@ [$$] START" >&2 | ||
+ | cmd1 | ||
+ | ret=1 | ||
+ | ret=4 | ||
+ | ret=2 | ||
+ | echo "$0 $@ [$$] SUCCESS" >&2 | ||
+ | [ $ret != 0 ] && exit $ret | ||
+ | exit 0 | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== strongbash008 ===== | ||
+ | |||
+ | **Нужно использовать shebang #!/bin/bash и даже в include**\\ | ||
+ | **Не нужно использовать shebang переносимости <del>#!/usr/bin/env bash</del>** | ||
+ | <hidden Почему...> | ||
+ | 1. Позволит проверять include на crab_syntax\\ | ||
+ | 2. #!/usr/bin/env не нужно использовать для /bin/bash тк 99,999 ОС linux bash лежит в /bin/bash и только если Вы хотите использовать код на не linux, тогда возможно потребуется /usr/bin/env\\ | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #ed1c24>Плохо</color> | ||
+ | <code bash> | ||
+ | #!/usr/bin/env bash | ||
+ | </code> | ||
+ | <color #22b14c>Хорошо</color> | ||
+ | <code bash> | ||
+ | #!/bin/bash | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ===== strongbash009 ===== | ||
+ | |||
+ | **Нельзя использовать команду let.** | ||
+ | <hidden Почему...> | ||
+ | Так как она дает ошибку при переходе через 0, let i=1-1 упадет скрипт. | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | Плохо: | ||
+ | <code bash> | ||
+ | let c=a-b | ||
+ | </code> | ||
+ | Хорошо:\\ | ||
+ | <code bash> | ||
+ | c=$((a-b)) | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== strongbash010 warning ===== | ||
+ | **Не рекомендуется использовать переменные, особенно глобальные, для временных файлов, если эта переменная используется менее 5 раз.**\\ | ||
+ | <del>$TMP_FILE=$( mktemp /tmp/tmp_file.XXXX )</del> | ||
+ | <hidden Почему...> | ||
+ | - Это усложняет чтение кода и понимание кода, файл сам по себе есть переменная и временная ссылка на него редко оправдана.\\ | ||
+ | - Плохо если приходится искать далеко вверху по коду, что это за переменная, и что там в этом файле.\\ | ||
+ | - mktemp это тоже плохо так как бессмысленно, если оч надо <code bash> | ||
+ | tt=/tmp/NAME_$((RANDOM)).$$ | ||
+ | </code>\\ | ||
+ | - Имя временного файла должно содержать в себе имя исполняемой программы и ее PID обязательно для разбора оставшихся после падения файлов.\\ | ||
+ | - Если файлов останется от падения слишком много, то скрипт не сможет работать тк пиды заполнятся и это нормально мы об этом узнаем. Либо можно сделать rm -f перед работой | ||
+ | </hidden> | ||
+ | |||
+ | <hidden Пример...> | ||
+ | <color #ed1c24>Плохо тк используется мало и глобальная переменная:</color> | ||
+ | <code bash> | ||
+ | $TMP_FILE_CAT_FROM=/tmp/${binname}_tmpls.$$ или $TMP_FILE_CAT_FROM=$( mktemp /tmp/tmpls.XXXX ) | ||
+ | ls /var/lib > $TMP_FILE_CAT_FROM | ||
+ | while read f; do | ||
+ | echo $f | ||
+ | done < $TMP_FILE_CAT_FROM | ||
+ | rm -f $TMP_FILE_CAT_FROM | ||
+ | </code> | ||
+ | <color #ed1c24>Плохо тк используется далеко от объявления и глобальная переменная:</color> | ||
+ | <color #ed1c24>(Иногда это нормально для временных каталогов)</color> | ||
+ | <code bash> | ||
+ | $TMP_FILE_CAT_FROM=/tmp/${binname}_tmpls.$$ или $TMP_FILE_CAT_FROM=$( mktemp /tmp/tmpls.XXXX ) | ||
+ | код1 | ||
+ | код2 | ||
+ | код3 | ||
+ | код4 | ||
+ | код5 | ||
+ | код6 | ||
+ | ls /var/lib > $TMP_FILE_CAT_FROM | ||
+ | while read f; do | ||
+ | echo $f | ||
+ | done < $TMP_FILE_CAT_FROM | ||
+ | ls /var/lib > $TMP_FILE_CAT_FROM | ||
+ | while read f1; do | ||
+ | echo $f1 | ||
+ | done < $TMP_FILE_CAT_FROM | ||
+ | ls /var/lib > $TMP_FILE_CAT_FROM | ||
+ | while read f2; do | ||
+ | echo $f2 | ||
+ | done < $TMP_FILE_CAT_FROM | ||
+ | rm -f $TMP_FILE_CAT_FROM | ||
+ | </code> | ||
+ | |||
+ | <color #22b14c>Хорошо локальное использование:</color> | ||
+ | <code bash> | ||
+ | ls /var/lib > /tmp/myname_tmpls.$$ | ||
+ | while read f; do | ||
+ | echo $f | ||
+ | done < /tmp/myname_tmpls.$$ | ||
+ | rm -f /tmp/myname_tmpls.$$ | ||
+ | </code> | ||
+ | |||
+ | |||
+ | <color #22b14c>Хорошо объявили и сразу используем много раз:</color> | ||
+ | <code bash> | ||
+ | $tmpls=/tmp/${binname}_tmpls.$$ или $tmpls=/tmp/${binname}_tmpls.$$.$(RANDOM) или $tmpls=$( mktemp /tmp/${binname}_tmpls.$$.XXXX ) | ||
+ | ls /var/lib > $tmpls | ||
+ | while read f; do | ||
+ | echo $f | ||
+ | done < $tmpls | ||
+ | echo ls /var/lib2 > $tmpls | ||
+ | cat lilo.conf > $tmpls | ||
+ | if grep ttt $tmpls; then | ||
+ | echo ddd | ||
+ | fi | ||
+ | rm -f $tmpls | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ### todo http://opencarbon.ru/соглашения_кода:strongbash_коменты | ||
+ | ===== strongbash011 ===== | ||
+ | **Максимальный indent вложенность 5**\\ | ||
+ | <hidden Почему...> | ||
+ | Большая вложенность всегда приводит к большой вложенности мысли при чтении кода, это неудобно и приводит к ошибкам.\\ | ||
+ | Используйте ранний return или сделайте подфункции, или case и тп.\\ | ||
+ | Не используйте большие if then else иначе мозг будет занят 5-ю уровнями вложенности.\\ | ||
+ | </hidden> | ||
+ | |||
+ | |||
+ | ===== strongbash012 ===== | ||
+ | **Команды | и || и && при переносе длинных строк** \\ | ||
+ | **требуется вынести на следующую строку и поставить tab.** | ||
+ | <hidden Почему...> | ||
+ | Это повышает читаемость кода и делает явным, что был перенос и это продолжение предыдущей строки. | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | cat 123 | grep 123 | sed | cat | grep \ | ||
+ | | while read t; do echo 123; done \ | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== strongbash013 ===== | ||
+ | **Ставьте пробел после #**\\ | ||
+ | **Можно используйте без пробелов #} и #{ для исправления ошибок crab_indent.** | ||
+ | |||
+ | ===== strongbash014 ===== | ||
+ | **Максимальный размер функции 64 строки, а лучше 32.** | ||
+ | <hidden Почему...> | ||
+ | Каждая функция это смысловая единица которая должна помещаться в пару предложений человеческого языка, это правильное нативное удобное для мозга деления на смыслы.\\ | ||
+ | </hidden> | ||
+ | <hidden Что делать если больше...> | ||
+ | Выделите подфункции, часто вида: | ||
+ | <code bash> | ||
+ | __fname(){ | ||
+ | } | ||
+ | </code> | ||
+ | При этом не нужно создавать миллиард функций на каждое словосочетание тк любая функция эта новая сущность, а плодить сущности плохо.\\ | ||
+ | И если функция из 4 строк вызывается один раз, то врядли она нужна.\\ | ||
+ | </hidden> | ||
+ | |||
+ | ===== strongbash015 ===== | ||
+ | **Слишком большой линейный файл больше 64 строк, выделите подфункции.** | ||
+ | <hidden Почему...> | ||
+ | Если в файле больше 64 строк, значит однозначно есть команды которые делают похожие вещи или группу вещей и правильней их объединить в функции.\\ | ||
+ | </hidden> | ||
+ | ===== strongbash016 ===== | ||
+ | |||
+ | **Уберите конечные space, tab.**\\ | ||
+ | **В конце файла должен стоять один enter.**\\ | ||
+ | |||
+ | ===== strongbash017 ===== | ||
+ | |||
+ | **Длина строки должна быть не более 100 символов. Используйте переносы '\'** | ||
+ | <hidden Почему...> | ||
+ | Человеческий мозг нормально воспринимает до 7-10 слов в одну ширину строки, если больше, то нужно делать перенос и желательно в точке новой подкоманды.\\ | ||
+ | Не нужно надеяться на встроенные псевдопереносы IDE средств, это совсем другое.\\ | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | echo "aaaaaaaaaaaaaaaaaaaaaa"\ | ||
+ | "bbbbbbbbbbbbbbbbbbb" | ||
+ | или | ||
+ | txt="aaaaaaaaaaaaaaaaaaaaaa"\ | ||
+ | "bbbbbbbbbbbbbbbbbbb" | ||
+ | или | ||
+ | echo "aaaaaaaaaaaaaaaaaaaaaa\ | ||
+ | bbbbbbbbbbbbbbbbbbb" | ||
+ | или | ||
+ | txt=\ | ||
+ | "aaaaaaaaaaaaaaaaaaaaaa | ||
+ | bbbbbbbbbbbbbbbbbbb" | ||
+ | или | ||
+ | iptables -t nat -I xge_pre -m addrtype ! --dst-type LOCAL \ | ||
+ | -m set ! --match-set xge_auth_list src \ | ||
+ | -m set ! --match-set xge_auth_list dst -j RETURN | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ===== strongbash018 ===== | ||
+ | |||
+ | **Поставьте пробел перед символом перенаправления '>'** | ||
+ | <hidden Пример...> | ||
+ | <code bash> | ||
+ | echo "text" > file.txt | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== strongbash019 ===== | ||
+ | **Добавьте описание и пример использования программы echo 'Info: Usage: Example: myname src dst'** | ||
+ | <hidden Пример...> | ||
+ | <color #22b14c>Хорошо:</color> | ||
+ | <code bash> | ||
+ | if [ "${1:---help}" = '--help' ]; then | ||
+ | echo 'Info: Автоматически создавать и коммитить все файлы в каталоге' | ||
+ | echo 'Usage: auto_git.sh [install] /var/www/html' | ||
+ | echo 'Example: auto_git.sh /var/www/html' | ||
+ | exit 0 | ||
+ | fi | ||
+ | </code> | ||
+ | <color #22b14c>Хорошо при include crab_sys.sh</color> | ||
+ | <code bash> | ||
+ | sys::usage "$@" и | ||
+ | ### --help Info: Автоматически создавать и коммитить все файлы в каталоге | ||
+ | ### --help Usage: auto_git.sh [install] /var/www/html | ||
+ | ### --help Example: auto_git.sh /var/www/html | ||
+ | sys::arg_parse "$@" | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ===== strongbash020 ===== | ||
+ | **Запрещены функции Log в утилитах**\\ | ||
+ | Запрещены любые LOG $(date) в утилитах, это можно только в демонах и в кроне | ||
+ | <code bash> | ||
+ | crab_logger {name_tool} &>>/var/log/name_tool.log | ||
+ | crab_exec -t timeout --daemon --log2 -u root --onlyonce --lock {name_tool} &>>/var/log/name_tool.log | ||
+ | # не утверждено как именно | ||
+ | </code> | ||
+ | |||
+ | ===== strongbash021 ===== | ||
+ | **1. Запрещено удалять tmp в trap EXIT.**\\ | ||
+ | **2. Не рекомендуется использовать trap EXIT.**\\ | ||
+ | <hidden Почему...> | ||
+ | 1. Создание временного файла это своего рода with file и его удаление это end with, чтоб было понтяно где он более не нужен аля область работы с файлом. И если файл остался в тмп, то это признак что есть ошибка в логике. | ||
+ | |||
+ | 2. Использование trap EXIT плохо тк он скрывает ошибки программ и никто о них не узнает, ломает честную логику алгоритма, это вредный хак. Должен быть явный вызов error_exit() и тп. | ||
+ | Если нужно удалить lock можно использовать trap TERM INT HUP, если lock остался после сбоя значит он и должен остаться, чтобы получить ошибки и разобраться со сбоем.\\ | ||
+ | </hidden> | ||
+ | ===== strongbash022 ===== | ||
+ | **1. Утилиты должны удалять за собой временные файлы**\\ | ||
+ | **2. Запрещено в утилитах передавать результаты через промежуточные файлы. Только errno stdout stderr.** | ||
+ | ** В исключительных случаях имя файла должно быть в argv или конфиг в argv.**\\ | ||
+ | Непонятно как проверять, что результаты идут через промежуточные файлы, пока проверяем, что чистится /tmp/\\ | ||
+ | |||
+ | ===== strongbash023 ===== | ||
+ | **1. Нужно всегда использовать read <color #ed1c24>-r</color> f1 f2 **\\ | ||
+ | **2. А для чтения целой строки while <color #ed1c24>IFS=<nowiki>''</nowiki></color> read -r line; do**\\ | ||
+ | <hidden Почему...> | ||
+ | 1. read -r иначе все esc последовательности обработаются, это вряд ли то что Вы хотели.\\ | ||
+ | 2. IFS=<nowiki>''</nowiki> иначе начальные пробелы и табы потеряются и строка будет не та, что в оригинале.\\ | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #ed1c24>Как не надо делать:</color> | ||
+ | <code bash> | ||
+ | read f1 f2 < /tmp/myfile | ||
+ | |||
+ | while read line; do | ||
+ | echo "$f" | ||
+ | done < /tmp/myfile | ||
+ | </code> | ||
+ | <color #22b14c>Как надо делать:</color> | ||
+ | <code bash> | ||
+ | read -r f1 f2 < /tmp/myfile | ||
+ | while IFS='' read -r line; do | ||
+ | echo "$f" | ||
+ | done < /tmp/myfile | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== strongbash024 ===== | ||
+ | ** 1. Запрещено rm -rf "/$dir" "./$dir" "$dir*" "$dir/*" "$dir/" **\\ | ||
+ | ** 2. rm -rf всегда с –one-file-system**\\ | ||
+ | |||
+ | <hidden Почему...> | ||
+ | 1. Это приводит к удалению незадуманных файлов если переменная пустая\\ | ||
+ | 2. При работе с контейнерами и виртуалками, очень велик риск удалить их rootfs, а используются они сейчас повсеместно.\\ | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #ed1c24>Как не надо делать:</color> | ||
+ | <code bash> | ||
+ | rm -rf "/$dir" | ||
+ | rm -rf "./$dir" | ||
+ | rm -rf "$dir*" | ||
+ | rm -rf "$dir/*" | ||
+ | rm -rf "$dir/" | ||
+ | </code> | ||
+ | <color #22b14c>Как надо делать:</color> | ||
+ | <code bash> | ||
+ | rm -rf -one-file-system "$dir" | ||
+ | rm -rf -one-file-system "/var/cache/mydir/*" | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ===== strongbash025 ===== | ||
+ | **"do" и "then" НЕ делаем c новой строки**\\ | ||
+ | <hidden Почему...>В большинстве языков принято в одной строке поэтому большинству так привычней.\\ | ||
+ | </hidden> | ||
+ | <hidden Пример...> | ||
+ | <color #ed1c24>Как не надо делать:</color> | ||
+ | <code bash> | ||
+ | if true; | ||
+ | then | ||
+ | echo true | ||
+ | fi | ||
+ | while read t; | ||
+ | do | ||
+ | echo $t | ||
+ | done | ||
+ | </code> | ||
+ | <color #22b14c>Как надо делать:</color> | ||
+ | <code bash> | ||
+ | if true; then | ||
+ | echo true | ||
+ | fi | ||
+ | while read t; do | ||
+ | echo $t | ||
+ | done | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ===== strongbash026 ===== | ||
+ | **Нельзя использовать голые скобки для assert и тп [ a = b ]**\\ | ||
+ | |||
+ | <hidden Почему...>Это противоречит явности кода и человекочитаемости ошибок.\\ | ||
+ | Это использование set -e для основной логики, что запрещено.\\ | ||
+ | </hidden>\\ | ||
+ | <hidden Пример...> | ||
+ | <color #ed1c24>Как не надо делать:</color> | ||
+ | <code bash> | ||
+ | [ "$fname" = "" ] | ||
+ | </code> | ||
+ | <color #22b14c>Как надо делать:</color> | ||
+ | <code bash> | ||
+ | [ "$fname" = "" ] && { echo 'Укажите имя файла'; exit 1; } | ||
+ | </code> | ||
+ | <color #22b14c>Как надо делать:</color> | ||
+ | <code bash> | ||
+ | # Или не проверять все подряд, тк все на свете проверить анрил, само упадет по set -e | ||
+ | </code> | ||
+ | </hidden> | ||
+ | ===== strongbash027 ===== | ||
+ | нельзя использовать конструкции вида [ a = b ] || cmd1 и cmd1 || cmd2 кроме утвержденного сахара\\ | ||
+ | используйте: | ||
+ | <code bash> | ||
+ | [ a != b ] && cmd1 и | ||
+ | if ! cmd1; then | ||
+ | cmd2 | ||
+ | fi | ||
+ | </code> | ||
+ | ===== strongbash028 ===== | ||
+ | **Нельзя использовать cmd1 || cmd2 || cmd3** лучше проверять окружение и вызвать сразу нужное If centos if ubintu if dir \\ | ||
+ | **Пример такой сахар разрешен, как разрешать отдельные ошибки команд не заглушая остальные**\\ | ||
+ | <code bash> | ||
+ | Если бы не существовало команды git status | ||
+ | error=$( git commit -m 'qqqqqqqqqqq' 2>&1 ) \ | ||
+ | || echo "$error" | grep -qm1 'nothing to commit' \ | ||
+ | || { echo "$error"; exit 1; } | ||
+ | или | ||
+ | error=$( git commit -m 'qqqqqqqqqqq' 2>&1 ) \ | ||
+ | || [[ $error != *"nothing to commit"* ]] && { echo "$error"; exit 1; } | ||
+ | </code> | ||
+ | **НО правильнее** | ||
+ | <code bash> | ||
+ | if ! git status | grep -qm1 'nothing to commit'; then | ||
+ | git commit -m 'qqqqqqqqqqq' | ||
+ | fi | ||
+ | </code> | ||
+ | |||
+ | ===== strongbash029 ===== | ||
+ | |||
+ | **Если мы берем stdout от функции мы обязаны прописать в первой строке Функции set -e тк он снимается** | ||
+ | |||
+ | |||
+ | ===== strongbash030 ===== | ||
+ | **Нельзя вызывать функцию внутри if fname и fname&& и fname||**\\ | ||
+ | тк set -e перестанет работать, перевыставить не поможет | ||
+ | ===== strongbash031 ===== | ||
+ | **нельзя использовать -EOF, используйте обычный EOF** | ||
+ | |||
+ | ===== strongbash032 ===== | ||
+ | **usage скрипта и его описание должно располагаться в начале скрипта**\\ | ||
+ | Чтобы при открытии файла сразу понять, что он делает. | ||
+ | |||
+ | ===== strongbash033 ===== | ||
+ | **1. Глобальная переменная должна быть в верхнем регистре, если используется в функциях.**\\ | ||
+ | **2. Все локальные переменные в функциях должны быть объявлены как local.**\\ | ||
+ | **2.1 Переменные наследуемые из env и тп можно объявить как declare в начале файла.**\\ | ||
+ | **3. Локальные переменные должны быть в нижнем регистре.**\\ | ||
+ | **4. Стараться не переопределять глобальные переменные в функциях, возможно станет правилом** | ||
+ | ===== strongbash034 ===== | ||
+ | **1. Нельзя при включенном set -o pipefail использовать grep -q grep -m 1 head tail**\\ | ||
+ | это приводит к падению левой части pipe.\\ | ||
+ | **2. set -o pipefail не работает в if $cmd; then и не нужно выставлять**\\ | ||
+ | нет смысла выставлять перед if\\ | ||
+ | **3. ipret=$(ip r g |grep src) Команда используется внутри pipe, ее код возврата не будет проанализирован**\\ | ||
+ | <hidden Что делать> | ||
+ | Вы можете:\\ | ||
+ | 1. Или использовать промежуточную переменную ret=$( $cmd )\\ | ||
+ | 2. Или использовать строкой выше set -o pipefail cmd set +o pipefail (кроме использования в if $cmd | cmd2)\\ | ||
+ | 4. Или если Вы считаете код достаточно надежным для конкретного случая, добавьте || true в конце\\ | ||
+ | 5. Или комментарий строкой выше # skip strongbash034\\ | ||
+ | </hidden> | ||
+ | <hidden Исключения> | ||
+ | Разрешены команды:\\ | ||
+ | grep sed tr awk tail head echo cat cut printf sort uniq wc | ||
+ | uname date locate which tac dos2unix unix2dos tee | ||
+ | iconv free lsmod dmidecode hostname netstat ps top | ||
+ | egrep fgrep while ls find | ||
+ | </hidden> | ||
+ | **4. Необходимо включить set -o pipefail для ( code ) | cmd**\\ | ||
+ | Иначе code будет работать без set -e\\ | ||
+ | **5. Все set -o pipefail нужно отключать после pipe set +o pipefail**\\ | ||
+ | **6. Нельзя устанавливать set -o pipefail на весь файл**\\ | ||
+ | |||
+ | ===== strongbash035 ===== | ||
+ | ** Нельзя использовать присвоение внутри if a=$(cmd); then **\\ | ||
+ | |||
+ | ===== strongbash036 todo ===== | ||
+ | Все аргументы нужно сохранять в глобальные переменные вида <code bash> | ||
+ | ARG_MYNAME=${1//--myname=/} | ||
+ | </code>\\ | ||
+ | или sys:parse_arg "$@" | ||
+ | |||
+ | ===== strongbash037 todo ===== | ||
+ | Запрещено использовать echo Без кавычек | ||
+ | <del>echo $tmp</del> | ||
+ | |||
+ | |||
+ | ===== TODO добавить в инструкцию как программировать на баше ===== | ||
+ | А пока читать коменты в http://opencarbon.ru/правила_разработки:как_надо_делать:разбор_crab_mysqldump2git | ||
+ | |||
+ | **TODO** Описать виды функций __funct funct и виды переменных ARG CONF GLOBAL local и namespace сабшелы\\ | ||
+ | **TODO** | ||
+ | В файл примера | ||
+ | |||
+ | Добавить в описание правил что grep -qm1 может уронить левую часть если это долгая команда и отгрепается первая строка пример | ||
+ | <code bash> | ||
+ | ( set -euEo pipefail; for((i=0;i<10;i++)); do echo q; sleep 1; done | grep -q q ); echo $? | ||
+ | </code> | ||
+ | |||
+ | Добавить в описание | ||
+ | для всего есть подходящие инструменты, bash отличный скриптовый язык, позволяющий делать мощные программы написав немного кода по вызову готовых утилит. | ||
+ | И при этом этот код легко сопровождать и разбирать потом без программиста, админам и саппорту. | ||
+ | Есть куча кейсов где программы на баше в 10 раз меньше, чем на питоне при этом надежней и написана за гораздо меньшее время. | ||
+ | |||
+ | ~~DISCUSSION~~ | ||
+ | |||
+ | /*Не удаляйте эту строку и ниже!*/ | ||
+ | {(rater>id=1|name=Прочитал_соглашения_кода:strongbash|type=vote|trace=user|tracedetails=1)} | ||
+ | |||
+ | |||
+ | ~~OWNERAPPROVE~~ | ||