Разрабатывайте скрипты с опциями set -eu
- set -e - падать на ошибках
- set -u - считать ошибкой обращение к неопределенной переменной
- set -o pipefail - заставить пайпы перестать скрывать ошибки предыдущих команд
- максимально быстрое выявление скрытых ошибок в коде.
- не надо самому писать многие проверки значений.
- Это сделает 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 прямо.
Примечание: конструкции вроде:
[ -n $something ] && echo OH ERROR
будут падать до вывода . Их тоже придётся "залепливать" || true, либо заменять на
if [ -n "$something" ] then
echo OH ERROR
fi
На мой взгляд, падают - и пусть падают, если это действительно ошибка. Не надо писать ручное падение, если результат не будет значительно отличаться. Несмотря на всю уродливость || true - такой код всё равно остаётся чище и проще, чем с ручной обработкой всех возможных ошибок.
Благодаря set -u код обрастает «костылями» немного другого рода:
variable="${1:-default_value}"
Зато код проверки переданных функциям параметров становится значительно чище (практически пропадает).
На мой взгляд - да. Developer вариант должен быть максимально близким к продакшну.
Частая аргументация против: "в идеале. реально при “set -e” часто получаем wtf, когда внезапно в один момент половина скрипта молча перестает работать".
Отвечу: вызываем c bash -x и половина скрипта перестаёт работать отнюдь не молча. Плюс у вызывающей стороны тоже должен быть включен set -e, чтобы вы узнали о том, что где-то что-то упало. Также избавиться от молчаливых падений можно с помощью конструкции вроде:
trap __exit EXIT
__exit() {
RETVAL=$?
if [ "$RETVAL" != 0 ]; then
echo -n "ERROR($RETVAL): $0 "
for ((i=${#FUNCNAME[@]}; i>0; i--)); do
echo -n "${FUNCNAME[$i-1]} "
done | sed -e 's/ $/\n/; s/ / -> /g'
fi
return $RETVAL
}
Ещё один аргумент: "мой посыл в том, что чаще всего лучше эти вещи проверять другим способом — явными проверками и разбиением на мелкие блоки".
Ответ: как только в скрипте появляется set -eu - вам приходится писать явные проверки, иначе скрипт падает. Если не появляется - вы не пишете все явные проверки, потому что вы человек. К слову, разбиение на мелкие блоки, лучше даже в виде утилит, действительно довольно хорошо помогает изолировать проблемное место.
@dshevchenko_biz подкинул ссылку на unofficial bash strict mode, в принципе он включает в себя совет по set -eu и ещё несколько важных вещей: http://redsymbol.net/articles/unofficial-bash-strict-mode/
Если вы используете 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
что даёт бонус в виде удобства тестирования и отладки. В подобном виде можно оформлять хуки /etc/sysconfig/.
Совет №4. Юзайте дебаг. В bash есть кое что, чего мне не хватает вообще ни в одном другом языке программирования.
bash -x - это автоматизированная система управления добавлением и выводом дебаговых принтов. На самом деле она крутая и сразу проясняет дело. Вывода полно, но зато вы узнаете абсолютно всё, что происходит под капотом, вызов каждой функции, её аргументы, и прочее прочее.
Пример:
$ cat x.sh
#!/bin/bash
x=10
y=20
z=$((x+y))
echo $z
$ ./x.sh
30
$ bash -x x.sh
+ x=10
+ y=20
+ z=30
+ echo 30
30
Вместе с stderr в /dev/null улетает ваше время на отладку.
Не надо плодить пайпы там, где не надо!
Плохо:
cat "$file" | grep "$pattern"
Хорошо:
grep "$pattern" "$file"
Кто работает в консольке медленно - тот всех бесит, особенно тех, кто стоит и смотрит как вы что-то пытаетесь показать, медленно набивая команды руками.
Итак, в любом уважающем себя и своих пользователей shell есть reverse search, вызываемый нажатием ctrl+r и вводом части команды. Из того, что приходит в голову: bash, ipython, psql. Не используя эту возможность, вы будете люто, бешенно злить коллег тем, что вы медленные, а зная про ctrl+shift+r (шаг назад в поиске) вы не будете злиться, пропустив нужную команду в поиске.
Совет №6: используйте современные утилиты, а не заброшенные авторами 10+ лет назад.
В первую очередь - выкиньте ifconfig/vconfig. Они отжили своё, вместо них давно уже появился унифицированный и простой iproute2. Вообще iproute2 втянул в себя довольно много всего:
- arp - ip neigh
- vconfig - ip link
- route - ip route/ip rule
- ifconfig - ip addr/ip link
Совет №6.1: иногда /proc/net/dev обработать проще, чем грепать вывод iproute2/ifconfig. Там нет информации об IP адресах, но статистику по потерям/пакетам/объёму трафика оттуда читать очень легко. По поводу грепа ip - полезный пример команды:
ip -o-4 addr show label eth*
- -o запись в 1 строчку
- -4 только ipv4
- label eth* - только ethernet
Отживает своё и netstat. На смену ему пришли ss и conntrack. Сталкивался я также недавно и с тем, что netstat не отображал 1 из сокетов nginx. Если не ошибаюсь, у него были проблемы с отображением при использовании нескольких тредов, память изменяет. Важная заметка: формат вывода ss, мягко говоря, слабо ориентирован на человека, в сравнении с тем же netstat. Читать это глазами ТЯЖЕЛОВАТО.
lshw вот был классным, но вроде тоже скоро исчезнет. Некоторое новое оборудование уже не отображает. Вместо lshw предлагается использовать lspci, lsusb и lscpu. ls /sys/pci /sys/cpu /sys/usb было бы забавнее, внутри возможно почти так и есть
Bash - довольно медленный язык. Но - в ваших силах не делать его еще медленнее своим неидеальным кодом. Изучите ВСЕ coreutils, grep, sed, awk. Научитесь в нужные моменты использовать builtin'ы bash - и тогда в некоторых случаях вы будете сильно выигрывать по скорости. Как правило наибольший прирост в скорости дают замены циклов и их тел на одну команду, избавление от лишних вызовов команд и пайпов. Плюс в баше есть возможность очень легко "параллелить":
for job in {1..10}; do
somecmd &
done
wait
Частый аргумент против сложных регулярок в sed: "если что-то будет повторяться или надо автоматизировать - есть питон".
На мой взгляд так появляются свои sed/awk, напианные на python, который тоже раскуривать надо. А регулярки от этого никуда не пропадают, зато появляется:
import re
re.match("абсолютно та же самая регулярка", ...)
На самом деле в некоторых случаях, написать простенькую утилитку на python - не зло. Просто стоит знать, что скорее всего её можно написать в 20-30 символов на bash + sed + grep, а не в 20-30 строк на python.
Вдобавок много команд можно хранить в sed-скрипте. Команда regexp - не обязательно должа быть однострочной.
$ cat x.sed
#!/usr/bin/env sed -E -f
# не материмся!
s/bullshit/something/g
# пишем умные комментарии
s/thing/wut/g
# ведь комментарии - благодать!
s/(some)(wut)/\2\1/g
$ echo bullshit | ./x.sed
wutsome
В итоге это можно юзать как утилиту, и не думать о его внутреннем устройстве и ужасе regex. То же самое относится к awk. Не обязательно держать всю сложность в одном скрипте, если её становится много - выносите. Проще жить будет.
Хорошо зная регулярные выражения, вы можете заменять здоровые цепочки | grep x | grep -v y | grep -c z одним egrep. К слову: читаемость может значительно ухудшиться, но можно присвоить строку регулярки переменной, название которой служит комментарием.
Есть еще крутая опция:
grep -f file_with_patterns
позволяет грепать сразу по куче паттернов, вместо вызова большого числа грепов в цикле. Учтите, что каждый греп - это:
- exec()
- read() всего грепаемого файла.
-f позволяет сделать дело за один read() и один exec().
Совет #7: xargs мало кому понятен, но позволяет писать эффективный и короткий код. В основном его используют после пайпов.
Например объем исходников на C в текущей директории:
find . -type f -name *.[ch] | xargs wc -l
Еще xargs позволяет рулить "threads pool'ом":
http://coldattic.info/shvedsky/pro/blogs/a-foo-walks-into-a-bar/posts/7
Типичное "неиспользование" xargs:
find $find_filter | while read filename; do
something $filename
done
find $find_filter | xargs something
echo "1
2
3
4
5" | head -n -2
выведет:
1
2
3
отрезав последние 2 строчки.
P.S: tail умеет нечто подобное, но там немного непонятно:
echo "1
2
3
4
5" | tail -n +3
3
4
5
он отрезает верхние N-1 строчек. Почему -1 - не совсем понятно, если кто прояснит - будет круто.
- пишем дотабывание для своих скриптов
- "лайфхак" в виде использования Makefile для дотабывания
- как писать код лаконично и понятно, типичные нагромождения которые делают джуниоры итд
- return нужен очень редко
- альтернативные шеллы (в плане кода, не в плане свистелок, подсветок, шоткатов)
- zsh
- csh
- sh
https://habrahabr.ru/company/mailru/blog/311762/