Strongbash

strongbash.txt | Хозяин: | Изменен: 14.01.2020 09:26 nikolay_carbonsoft Черновик Новейший утвержденный

TODO_OSV возможно выборочно проверять номера из https://github.com/koalaman/shellcheck

Обязательно должен быть выполнен crab_indent.

Почему…

Почему…

  1. indent делает код более читаемым
  2. использование indent позволяет обнаруживать ошибки

Пример…

Пример…

Конструкция if-then располагаются в одной строке.
Хорошо:

if [ "$EUID" -ne "0" ]; then
      echo "Please run as root"
fi

Фигурная скобка «{« для определния тела функции должна быть в одной строке с функцией.
После переноса строки добавляем <Tab>.
Хорошо:

myfunc1() {
      commands...
}

Объявление функции без ключевого слова function.
Ключевое слово function было введено в ksh.
POSIX стандартизирует только синтаксис funcname().
Код в скобках с отступами.
Хорошо:

myfunc1() {
      echo "Hello world"
}

Плохо:

function myfunc1 {
      echo "Hello world"
}

Инструкция case без оступов:
Хорошо:

case "$variable" in
"$condition1" )
      command...
      ;;
 
"$condition2" )
      command...
      ;;
 
esac


Хорошо:

#!/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

Должен быть установлен set -eu или include crab_sys.sh
Категорически нельзя ставить set -o pipefail на весь файл см strongbash034_6

ВНИМАНИЕ set -eu это самое главное правило, оно дает режим strong.
Позволяет отлавливать и исключать неявное и непредсказуемое поведение программы, а также быстрей отлаживать.

ОЧЕНЬ БОЛЬШОЕ ВНИМАНИЕ set -eu используется для того, чтобы отловить неизвестные будущие ошибки программы и не дать ей выполняться далее.
set -eu не должен использоваться для реализации какой либо логики основного алгоритма, другими словами программа с set -eu и без set -eu должна работать одинаково в основной логике.

Что включает…

Что включает…

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.

Как не надо делать…

Как не надо делать…

Плохо делать ассерты вида:

[ $# = 3 ]

Хорошо явно отлавливать и писать текстом пользователю о заранее известных ошибках:

[ $# != 3 ] && { echo 'Укажите 3 параметра'; exit 1; }

Плохо пытаться использовать возможности set -e для формирования кода возврата функций:

set -e
func1(){
        git log > /tmp/myqqq.txt
        grep qqq /tmp/myqqq.txt 
        # ^^^ не надо надеяться на то, что здесь упадет и функция вернет не ноль, здесь не остановится выполнение функции.
        # такой подход технически невозможен ни с ни без set -e
        return 0
}
### это работать не будет
func1 && echo "qqq найден"

Плохо использовать стандартный кода возврата функций тк это отключает set -e:

set -e
func1(){
        set -e ### здесь писать бесполезно опция работать не будет тк мы берем код возврата функции
        git log > /tmp/myqqq.txt ### если здесь ошибка, то мы ее не поймаем и не упадем тк set -e отключится
        grep qqq /tmp/myqqq.txt && return 0 || return $?
}
### это будет работать, но потенциальные ошибки функции не приведут к падению скрипта и код становится менее надежным
func1 && echo "qqq найден"

Хорошо:

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 найден"

Для паранои:

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 найден"

Проблемы и решения set -e в условиях и функциях

Проблемы и решения set -e в условиях и функциях

Проблема, если мы вызываем функцию и анализируем ее код возврата
в ней перестает работать set -e :

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

Решение, можно использовать echo TRUE вместо return 0 :

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' }


Проблема set -e не работает в условиях:

# если команда ip упадет мы об этом не узнаем( хотя иногда это может быть и неважно для логики).
if ip r g 1 | grep 192.168.1.1; then
        echo "Шлюз указан верно"
fi

Решение, можно использовать промежуточную переменную:

ip_ret="$( ip r g 1 )"
if echo "$ip_ret" | grep 192.168.1.1; then
        echo "Шлюз указан верно"
fi      

Решение2, можно использовать промежуточную переменную:

ip_ret="$( ip r g 1 )" || true
if [ -z "$ip_ret" ] then
        echo "Не можем получить шлюз"
fi      

Непривычность set -u

Непривычность set -u

Проблема set -u иногда непривычен для входящих параметров:

src=$1   # если $1 не передан, то программа упадет

Решение, можно использовать default значения переменных:

echo ${1:-defaulttxt}
[ "${1:---help}" = '--help' ] && { echo 'Example: $0 src dst'; exit 0; }

Или учитывая правило skill -1 сделать так:

[ "${1:-}" = '' -o "${1:-}" = '--help' ] && { echo 'Example: $0 src dst'; exit 0; }
или
my_src="${1:-}"

Нельзя использовать || true в условных выражениях.

Почему…

Почему…

Это миф что без них не работает и все падает при set -e.
Все отлично работает, если return 0 в конце каждой функции и exit 0 в конце каждого файла.

Пример…

Пример…

Как не надо делать:

funct1(){
        [ a = b ] && echo Привет || true
}

Как надо делать:

funct1(){
        [ a = b ] && echo Привет
        return 0
}

Нельзя использовать else true в условных выражениях
см strongbash003

В конце каждой функции должен быть return 0 или return $ret или exit 0 или exit $ret

Почему…

Почему…

Это обязательно, чтобы не передать случайно неявное и нежелаемое значение последнего if, вместо явного $?.

Пример…

Пример…

Плохо:

prepare_text(){
        local text="${1:-}"
        [ "$text" = "" ] && echo "Привет" 
        ### "вот здесь мы отловили код возврата и не должны упасть, но мы упадем сразу после выхода из функции"
        ### тк вернется последний код возврата
};
arg1=$( prepare_text "555" )
echo $arg1

Хорошо:

prepare_text(){
        local text="${1:-}"
        [ "$text" = "" ] && echo "Привет" 
        return 0   ### мы всегда явно задаем, что мы хотим вернуть либо return 0 либо return $ret
};
arg1=$( prepare_text "555" )
echo $arg1

В начале каждого файла должен быть echo START и USAGE, INFO, EXAMPLE
В конце каждого файла должны быть echo SUCCESS и exit 0 :
Для продвинутых разработчиков лучше использовать в начале скрипт: include crab_sys.sh

Почему…

Почему…

  1. exit 0 для того чтобы избежать случайный код возврата.
  2. USAGE, INFO, EXAMPLE использования важен, чтобы программа могла жить в будущем и не была выброшена.
  3. echo START echo SUCCESS делает удобным использование и чтение консоли и логов.
  4. include crab_sys.sh позволит автоматически деалать echo START echo SUCCESS echo FAIL sys::usage «$@» # –help и показывает callstack при падении

Пример…

Пример…

Хорошо:

#!/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

Хорошо сделать функцию __exit(), если много exit 0 в программе

#!/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

Хорошо для продвинутых и для карбон софт:

#!/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

Решение проблем…

Решение проблем…

Хорошо для тихих файлов:

# 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

Или для тихих файлов с crab_sys.sh:

__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

В конце каждого файла должен быть exit 0 или для библиотек: # exit 0.

Почему…

Почему…

Мы должны явно задавать все легальные выходы из программы аналогично как с функциями.
exit 0 в отличии от exit $ret дополнительно защитит от будущих ошибок при рефакторинге кода.

Пример…

Пример…

Хорошо

#!/bin/bash
echo "$0 $@ [$$] START" >&2
cmd1 
[ $sz -gt 110 ] && { echo "Ошибка sz=$sz"; exit 2; }
cmd2
cmd3
echo "$0 $@ [$$] SUCCESS" >&2
exit 0

Плохо exit $ret тк в будущем при рефакторинге может появиться неоднозначности

#!/bin/bash
echo "$0 $@ [$$] START" >&2
cmd1 
[ $sz -gt 110 ] && { echo "Ошибка sz=$sz"; exit 2; }
cmd2
cmd3 || ret=$?
echo "$0 $@ [$$] SUCCESS" >&2
exit $ret

Хорошо. Если Ваш файл должен возвращать разные коды легально, то нужно это сделать явно :

#!/bin/bash
echo "$0 $@ [$$] START" >&2
cmd1
ret=1
ret=4
ret=2
echo "$0 $@ [$$] SUCCESS" >&2
[ $ret != 0 ] && exit $ret
exit 0

Нужно использовать shebang #!/bin/bash и даже в include
Не нужно использовать shebang переносимости #!/usr/bin/env bash

Почему…

Почему…

1. Позволит проверять include на crab_syntax
2. #!/usr/bin/env не нужно использовать для /bin/bash тк 99,999 ОС linux bash лежит в /bin/bash и только если Вы хотите использовать код на не linux, тогда возможно потребуется /usr/bin/env

Пример…

Пример…

Плохо

#!/usr/bin/env bash

Хорошо

#!/bin/bash

Нельзя использовать команду let.

Почему…

Почему…

Так как она дает ошибку при переходе через 0, let i=1-1 упадет скрипт.

Пример…

Пример…

Плохо:

let c=a-b

Хорошо:

c=$((a-b))

Не рекомендуется использовать переменные, особенно глобальные, для временных файлов, если эта переменная используется менее 5 раз.
$TMP_FILE=$( mktemp /tmp/tmp_file.XXXX )

Почему…

Почему…

  1. Это усложняет чтение кода и понимание кода, файл сам по себе есть переменная и временная ссылка на него редко оправдана.
  2. Плохо если приходится искать далеко вверху по коду, что это за переменная, и что там в этом файле.
  3. mktemp это тоже плохо так как бессмысленно, если оч надо
    tt=/tmp/NAME_$((RANDOM)).$$


  4. Имя временного файла должно содержать в себе имя исполняемой программы и ее PID обязательно для разбора оставшихся после падения файлов.
  5. Если файлов останется от падения слишком много, то скрипт не сможет работать тк пиды заполнятся и это нормально мы об этом узнаем. Либо можно сделать rm -f перед работой

Пример…

Пример…

Плохо тк используется мало и глобальная переменная:

$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

Плохо тк используется далеко от объявления и глобальная переменная: (Иногда это нормально для временных каталогов)

$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

Хорошо локальное использование:

ls /var/lib > /tmp/myname_tmpls.$$
        while read f; do
echo $f
done < /tmp/myname_tmpls.$$
rm -f /tmp/myname_tmpls.$$

Хорошо объявили и сразу используем много раз:

$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

### todo http://opencarbon.ru/соглашения_кода:strongbash_коменты

Максимальный indent вложенность 5

Почему…

Почему…

Большая вложенность всегда приводит к большой вложенности мысли при чтении кода, это неудобно и приводит к ошибкам.
Используйте ранний return или сделайте подфункции, или case и тп.
Не используйте большие if then else иначе мозг будет занят 5-ю уровнями вложенности.

Команды | и || и && при переносе длинных строк
требуется вынести на следующую строку и поставить tab.

Почему…

Почему…

Это повышает читаемость кода и делает явным, что был перенос и это продолжение предыдущей строки.

Пример…

Пример…

Хорошо:

cat 123 | grep 123 | sed | cat | grep \
        | while read t; do echo 123; done \

Ставьте пробел после #
Можно используйте без пробелов #} и #{ для исправления ошибок crab_indent.

Максимальный размер функции 64 строки, а лучше 32.

Почему…

Почему…

Каждая функция это смысловая единица которая должна помещаться в пару предложений человеческого языка, это правильное нативное удобное для мозга деления на смыслы.

Что делать если больше…

Что делать если больше…

Выделите подфункции, часто вида:

__fname(){
}

При этом не нужно создавать миллиард функций на каждое словосочетание тк любая функция эта новая сущность, а плодить сущности плохо.
И если функция из 4 строк вызывается один раз, то врядли она нужна.

Слишком большой линейный файл больше 64 строк, выделите подфункции.

Почему…

Почему…

Если в файле больше 64 строк, значит однозначно есть команды которые делают похожие вещи или группу вещей и правильней их объединить в функции.

Уберите конечные space, tab.
В конце файла должен стоять один enter.

Длина строки должна быть не более 100 символов. Используйте переносы '\'

Почему…

Почему…

Человеческий мозг нормально воспринимает до 7-10 слов в одну ширину строки, если больше, то нужно делать перенос и желательно в точке новой подкоманды.
Не нужно надеяться на встроенные псевдопереносы IDE средств, это совсем другое.

Пример…

Пример…

Хорошо:

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

Поставьте пробел перед символом перенаправления '>'

Пример…

Пример…

echo "text" > file.txt

Добавьте описание и пример использования программы echo 'Info: Usage: Example: myname src dst'

Пример…

Пример…

Хорошо:

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

Хорошо при include 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 "$@"

Запрещены функции Log в утилитах
Запрещены любые LOG $(date) в утилитах, это можно только в демонах и в кроне

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
# не утверждено как именно

1. Запрещено удалять tmp в trap EXIT.
2. Не рекомендуется использовать trap EXIT.

Почему…

Почему…

1. Создание временного файла это своего рода with file и его удаление это end with, чтоб было понтяно где он более не нужен аля область работы с файлом. И если файл остался в тмп, то это признак что есть ошибка в логике.

2. Использование trap EXIT плохо тк он скрывает ошибки программ и никто о них не узнает, ломает честную логику алгоритма, это вредный хак. Должен быть явный вызов error_exit() и тп. Если нужно удалить lock можно использовать trap TERM INT HUP, если lock остался после сбоя значит он и должен остаться, чтобы получить ошибки и разобраться со сбоем.

1. Утилиты должны удалять за собой временные файлы
2. Запрещено в утилитах передавать результаты через промежуточные файлы. Только errno stdout stderr. В исключительных случаях имя файла должно быть в argv или конфиг в argv.
Непонятно как проверять, что результаты идут через промежуточные файлы, пока проверяем, что чистится /tmp/

1. Нужно всегда использовать read -r f1 f2
2. А для чтения целой строки while IFS='' read -r line; do

Почему…

Почему…

1. read -r иначе все esc последовательности обработаются, это вряд ли то что Вы хотели.
2. IFS='' иначе начальные пробелы и табы потеряются и строка будет не та, что в оригинале.

Пример…

Пример…

Как не надо делать:

read f1 f2 < /tmp/myfile
 
while read line; do
        echo "$f"
done < /tmp/myfile

Как надо делать:

read -r f1 f2 < /tmp/myfile
while IFS='' read -r line; do
        echo "$f"
done < /tmp/myfile

1. Запрещено rm -rf »/$dir» «./$dir» «$dir*» «$dir/*» «$dir/»
2. rm -rf всегда с –one-file-system

Почему…

Почему…

1. Это приводит к удалению незадуманных файлов если переменная пустая
2. При работе с контейнерами и виртуалками, очень велик риск удалить их rootfs, а используются они сейчас повсеместно.

Пример…

Пример…

Как не надо делать:

rm -rf "/$dir"
rm -rf "./$dir"
rm -rf "$dir*"
rm -rf "$dir/*"
rm -rf "$dir/" 

Как надо делать:

rm -rf -one-file-system "$dir"
rm -rf -one-file-system "/var/cache/mydir/*"

«do» и «then» НЕ делаем c новой строки

Почему…

Почему…

В большинстве языков принято в одной строке поэтому большинству так привычней.

Пример…

Пример…

Как не надо делать:

if true;
then
        echo true
fi
while read t;
do
        echo $t
done

Как надо делать:

if true; then
        echo true
fi
while read t; do
        echo $t
done

Нельзя использовать голые скобки для assert и тп [ a = b ]

Почему…

Почему…

Это противоречит явности кода и человекочитаемости ошибок.
Это использование set -e для основной логики, что запрещено.


Пример…

Пример…

Как не надо делать:

[ "$fname" = "" ]

Как надо делать:

[ "$fname" = "" ] && { echo 'Укажите имя файла'; exit 1; }

Как надо делать:

# Или не проверять все подряд, тк все на свете проверить анрил, само упадет по set -e

нельзя использовать конструкции вида [ a = b ] || cmd1 и cmd1 || cmd2 кроме утвержденного сахара
используйте:

[ a != b ] && cmd1 и 
if ! cmd1; then
        cmd2
fi

Нельзя использовать cmd1 || cmd2 || cmd3 лучше проверять окружение и вызвать сразу нужное If centos if ubintu if dir
Пример такой сахар разрешен, как разрешать отдельные ошибки команд не заглушая остальные

Если бы не существовало команды 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; }

НО правильнее

if ! git status | grep -qm1 'nothing to commit'; then
       git commit -m 'qqqqqqqqqqq'
fi

Если мы берем stdout от функции мы обязаны прописать в первой строке Функции set -e тк он снимается

Нельзя вызывать функцию внутри if fname и fname&& и fname||
тк set -e перестанет работать, перевыставить не поможет

нельзя использовать -EOF, используйте обычный EOF

usage скрипта и его описание должно располагаться в начале скрипта
Чтобы при открытии файла сразу понять, что он делает.

1. Глобальная переменная должна быть в верхнем регистре, если используется в функциях.
2. Все локальные переменные в функциях должны быть объявлены как local.
2.1 Переменные наследуемые из env и тп можно объявить как declare в начале файла.
3. Локальные переменные должны быть в нижнем регистре.
4. Стараться не переопределять глобальные переменные в функциях, возможно станет правилом

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, ее код возврата не будет проанализирован

Что делать

Что делать

Вы можете:
1. Или использовать промежуточную переменную ret=$( $cmd )
2. Или использовать строкой выше set -o pipefail cmd set +o pipefail (кроме использования в if $cmd | cmd2)
4. Или если Вы считаете код достаточно надежным для конкретного случая, добавьте || true в конце
5. Или комментарий строкой выше # skip strongbash034

Исключения

Исключения

Разрешены команды:
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

4. Необходимо включить set -o pipefail для ( code ) | cmd
Иначе code будет работать без set -e
5. Все set -o pipefail нужно отключать после pipe set +o pipefail
6. Нельзя устанавливать set -o pipefail на весь файл

Нельзя использовать присвоение внутри if a=$(cmd); then

Все аргументы нужно сохранять в глобальные переменные вида

ARG_MYNAME=${1//--myname=/}


или sys:parse_arg «$@»

Запрещено использовать echo Без кавычек echo $tmp

Нужно всегда использовать переменные и аргументы в кавычках.

Нельзя:
if [ $1 = info ]; then
    echo $1
fi
 
Нужно:
if [ "$1" = 'info' ]; then
    echo "$1"
fi

А пока читать коменты в http://opencarbon.ru/правила_разработки:как_надо_делать:разбор_crab_mysqldump2git

TODO Описать виды функций __funct funct и виды переменных ARG CONF GLOBAL local и namespace сабшелы
TODO В файл примера

Добавить в описание правил что grep -qm1 может уронить левую часть если это долгая команда и отгрепается первая строка пример

 ( set -euEo pipefail; for((i=0;i<10;i++)); do echo q; sleep 1; done | grep  -q q ); echo $?

Добавить в описание для всего есть подходящие инструменты, bash отличный скриптовый язык, позволяющий делать мощные программы написав немного кода по вызову готовых утилит. И при этом этот код легко сопровождать и разбирать потом без программиста, админам и саппорту. Есть куча кейсов где программы на баше в 10 раз меньше, чем на питоне при этом надежней и написана за гораздо меньшее время.

Прочитал соглашения кода strongbash
Yes(0) No(1) Clear

Yes:
Не прочитано ()!

No:
,

~~OWNERAPPROVE~~

Олег СтрижеченкоОлег Стрижеченко, 08.10.2018 08:22
strongbash018

Как правильно?

{
        echo qwe
} &>/dev/null

или

{
        echo qwe
} &> /dev/null

Иными словами: правило описывает пробел слева, справа или с обоих сторон вокруг символа перенаправления вывода?

+ как быть с перенаправлениями в другой дескриптор?

2>&1

Если пробел справа важен, то есть ли разница между абсолютным и относительным путём к файлу?

echo qwe &>/dev/null
echo > file.txt
echo >file.txt

В общем нужно чуток пополнить число примеров, дабы не смущать Колю.

Sergey OsintsevSergey Osintsev, 06.11.2018 09:10

Если файл то пробел справа обязателен если дескриптор то запрещен

adminadmin, 07.11.2018 09:26

Поставьте пробел перед символом перенаправления

Сергей ТрошинСергей Трошин, 31.10.2018 08:37 (31.10.2018 08:38)

crab-indent не работает с

cat <<"EOF"
...
EOF

:(

Sergey OsintsevSergey Osintsev, 06.11.2018 09:12

а зачем кавычки? без них все работает

Сергей ТрошинСергей Трошин, 08.11.2018 05:45

Если не ставить кавычки, то баш выполняет подстановки в тексте. Альтернатива кавычкам, эскейпить все спец-символы в тексте. Когда спец-символов много, и чтение, и написание кода, может превратиться в ад.

adminadmin, 13.11.2018 11:50

Ясно тогда надо мердж реквест, у меня нет времени решать эту задачу

adminadmin, 04.12.2018 07:27

TODO По мотивам идей Олега. думаю надо добавить shellcheck в качестве варнинг проверок. Но опять же grep -с и ${#name} не каждый по памяти помнит и лоускил правило нарушено. read -r это полезно, и вообще бы все это проанализировать и полезное выделить цифрами и грепать их дополнительно в выводе shellcheck. Добавил в туду

НиколайНиколай, 28.03.2019 06:56

Добавить проверку, чтобы вместо ln -fs делали ln -nfs, иначе внутри папки создается симлинк.

UPD: всегда делать ln -ns

adminadmin, 19.10.2019 14:01

+

Сергей ТрошинСергей Трошин, 03.06.2019 07:42
Запрещено использовать echo Без кавычек echo $tmp

Думаю нужно правило: всегда использовать кавычки, кроме случаев, где необходим word split. #skipcheck в таких случаях будет говорить, что это кавычки пропущены умышленно.

adminadmin, 19.10.2019 14:01

Согласен добавь

Сергей ТрошинСергей Трошин, 27.07.2020 10:39

Правило: по аналогии с set -eu всегда ставить в шапку shopt -s nullglob (если файлы по маске не находятся, то результат - пусто, а не само выражение).

Сейчас регулярно случаются баги, когда * не разворачивается. И для фикса используют дополнительные условия по проверки результата развёртывания *.

Случаи когда этот флаг не нужен в скрипте, скорее всего баг.

Есть ещё хороший флаг dotglob, но его полезность зависит от ситуации. И работает он совместно с переменной GLOBIGNORE.

Артем ПолухинАртем Полухин, 19.11.2020 00:37

По поводу нуллглоба: мне кажется что иногда лучше оставить неразвернутое выражение и получить сообщение об ошибке «Нет такого файла/директории», чем получить пустоту и непредсказуемое поведение утилит, которые принимают позиционные аргументы.

Ваш комментарий. Вики-синтаксис разрешён: