Skip to content

Instantly share code, notes, and snippets.

@kirs
Forked from strizhechenko/shell.md
Created July 5, 2016 19:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kirs/79767402c86ed7899217087279cdcab9 to your computer and use it in GitHub Desktop.
Save kirs/79767402c86ed7899217087279cdcab9 to your computer and use it in GitHub Desktop.
Программа для underhood.ko

Опции bash для разработки

Разрабатывайте скрипты с опциями set -eu

  • set -e - падать на ошибках
  • set -u - считать ошибкой обращение к неопределенной переменной

Профиты

  • максимально быстрое выявление скрытых ошибок в коде.
  • не надо самому писать многие проверки значений.
  • Это сделает shell-код похожим на нормальный язык программирования, а не на набор последовательно выполняющихся команд, которым на всё пофиг.

Минусы

«Ложные» и ложные падения. Про них расскажу подробнее. Вот пример скрипта:

#!/usr/bin/env bash

set -eu

x=1
((x++))
echo $x
x=0
((x++))
echo $x

Казалось бы, он должен вывести:

2
1

Но в bash версии 4+ увеличение переменной до единицы даё код возврата 1. Вроде даже находил почему и даже подумал что «ок, логично». Ещё часто «ложным» падением заканчивается возврат

cmd | grep "something"

при обработке текста, если этого something в stdin не было. В итоге код с set -e иногда обрастает конструкциями вроде

cmd | grep "something" || true

Суть set -e в том, чтобы все ошибки были обработаны. Go style прямо.

Несмотря на всю уродливость оного - такой код всё равно остаётся чище и проще, чем с ручной обработкой всех возможных ошибок. Благодаря set -u код обрастает «костылями» немного другого рода:

variable="${1:-default_value}"

Зато код проверки переданных функциям параметров становится значительно чище (практически пропадает).

Стоит ли включать их в продакшне?

На мой взгляд - да. Developer вариант должен быть максимально близким к продакшну.

Bash unofficial strict mode

@dshevchenko_biz подкинул ссылку на unofficial bash strict mode, в принципе он включает в себя совет по set -eu и ещё несколько важных вещей: http://redsymbol.net/articles/unofficial-bash-strict-mode/

Shellcheck и статический анализ

Если вы используете bash в качестве языка программирования, то и относитесь к нему как к языку и используйте статический анализатор

Самый популярный анализатор для shell/bash на текущий момент - shellcheck. Есть плагины к Atom, можно запускать из консольки. Сейчас проект активно развивается, так что рекомендую следить за тем, насколько старую версию вы используете. К примеру за время которое я его использую (около полугода) пофиксили около трёх досадных багов, которые ложно обвиняли меня в плохом коде. Одного факта поиска неиспользуемых переменных уже достаточно, но он гораздо умнее, находит кучу подводных камней и must-have для джуниоров.

С помощью вот такого простого скрипта можно определять первичные цели для рефакторинга репозитория с bash-скриптами.

Вообще корень многих зол в bash - передача аргументов. Всегда вот все с ней тупят, мучаются и велосипедят. 190% людей путают

$*
$@
"$*"
"$@"
  • 100% - это до того, как узнали чем они отличаются.
  • 90% - после.

shellcheck может предупредить вас о ситуации:

rm -rf $DIRECTORY/$file

которая, если не использовать set -eu может превратится в

rm -rf /

в принципе современный coreutils итак не даст вам этого сделать, но ведь не только в / аргумент может схлопнуться.

Но в принципе писать код можно и без shellcheck, следуя двум правилам:

  • кавычьте все "$переменные" в 99.9% случаев
  • тестируйте упорнее

Структурирование кода

Bash точно такой же язык программирования и к нему действуют те же правила разработки, что и на других языках - никто не любит много кода.

Модуль = утилита

Если в большинстве языков программирования эта проблема решается библиотеками, в bash лучше оформлять свой код в виде утилит. Принципиальной разницы между вызовом скрипта (читай команды) и функции нет.

Можно применять архитектурный подход, используемый в git - в зависимости от первого параметра ($1) запускать скрипт расположенный в, "$0-$1" которому прозрачно пробрасывать все параметры кроме первого. Дерево команд гита довольно здоровое, около 160 утилит, сам же git является маааленьким бинариком.

Почему в баше не очень принято делать библиотеки

Конечно, никто не запрещает использовать конструкцию

source lib/yourlib.sh
source lib/yourlib2.sh

можно также писать

. lib/yourlib.sh

Но тут к нам всплывает один неприятный момент bash - переменные по умолчанию имеют глобальную область видимости. Sub-утилиту же гораздо легче подвернуть функциональному тестированию, при необходимости, да и просто запустить при разработке. Да и в коде это будет выглядеть понятнее. Сравните:

source ./mylib.sh
…
rate_my_code .
…

или

…
./rate_my_code.sh .
…

Во втором случае недоумения «откуда этот rate_my_code взялся?» у читателя не возникает. Также у rate_my_code.sh может быть свой --help, объясняющий что и как.

В питоне этот момент хорошо сглаживается прекрасным механизмом импортов, где всё явно:

from lib.sublib import func

сразу видно откуда берётся func.

Переопределения функций

Вы можете переопределять переменные, функции итд, подключая другие скрипты как библиотеки в нужный момент. Это может показать здоровским, но иногда может привести к тому, что необходимость обеспечивать обратную совместимость с кодом клиентов (если у проекта агрессивное автоматическое обновление) приведёт к появлению 700+строчных монстров, которые хочется отрефакторить и разбить на отдельные модули, но нельзя.

Как делать и либу и утилиту в одном

В конструкции

source libname.sh

есть возможность передачи аргументов. А аргументы можно анализировать. Благодаря этому можно получить что-то похожее на конструкцию:

if __name__ == '__main__' :

в python:

func1() {
    echo qwe
}

# в конце файла
main() {
    cmd1
    cmd2
    if somelogic "$@"; then
        onemorecmd
    fi
}

if [ "${1:-}" != ':' ]; then
    main "$@"
fi

В результате можем получить в интерактивном шелле доступ ко всем функциям, определённым в скрипте:

$ source libname.sh :
$ func1
qwe

что даёт бонус в виде удобства тестирования и отладки.

дебаг, xtrace, PS1,2,3,4, reverse search

bash -x

что и как можно напихать в отладочный вывод

скорость работы в shell.

Правило про "лучший код - тот, который не был написан" действует и в bash, с заменой "код" на "команда".

reverse search.

пишем дотабывание для своих скриптов

"лайфхак" в виде использования Makefile для дотабывания

"modern" linux utils, что устарело, что пришло на замену

iproute2

прочее

несколько способов сгенерить 10000 exec() вместо 2-3

grep в циклах

while read vs xargs

использование builtin

как писать код лаконично и понятно, типичные нагромождения которые делают джуниоры итд

return нужен очень редко

альтернативные шеллы (в плане кода, не в плане свистелок, подсветок, шоткатов)

zsh

csh

sh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment