0
для true
и !0
для false
Использование 0
для true
и ненулевого значения для false
имеет смысл для bash, потому что при неудачном выполнении программы нужно вернуть код ошибки (которых много), в то время как при успешном завершении достаточно кода 0
.
Код завершения конвейера
В случае с конвейером в $?
помещается статус именно последней команды.
ls | grep pdf | wc
Конвейер выше будет “истиннным”, даже если grep
не найдет ничего, ибо wc
выполнится успешно и выведет:
0 0 0
+ Для некоторых программ отстуствие ожидаемого “успешного” результата не ошибка.
Соус: Книга Bash и кибербезопасность → Глава 2. Основы работы с bash
”Dry run” команд
Чтобы посмотреть, как будет выглядеть какая-то команда при выполнении - нужно просто написать перед командой echo, которая выведет команду, интерпретировав всякие переменные и т.д.:
for file in *.JPEG; do echo mv -v $file ${file/JPEG/jpg}; done
Соус: Книга “Идиомы Bash” → Глава 2. Язык циклов → Разработка и тестирование циклов for
Эффективный перебор int значений
Если важна эффективность при переборе целочисленных значений - можно объявить переменную цикла как целочисленную:
declare -i someCycleVar
Это позволит избежать ресурсоемких преобразований из строки в число и обратно.
Соус: Книга “Идиомы Bash” → Глава 2. Язык циклов → В заключение: стиль и удобочитаемость
Встроенные функции эффективнее команд
Юзать встроенные функции и ключевые слова эффективнее выполнения команд (которые исполняются во внешних файлах), особенно если зациклить их и вызывать много раз подряд.
Соус: Книга “Bash и кибербезопасность” → Глава 1. Работа с командной строкой → Основы работы с командной строкой → Команды, аргументы, встроенные функции и ключевые слова
Шаблоны - это не RegEx
Сопоставление c шаблоном в bash не является сопоставлением с регулярным выражением
В bash regex-ы поддерживаются только в операторе if
, если использовать оператор сравнения =~
.
Сопоставление с шаблоном или wildcarding в bash
Справки в скриптах
Не бойся добавлять даже большой справочный текст / справочные таблицы в конец скрипта. Функциональная часть будет находиться сверху, а справка снизу на случай, если пригодится. Одно другому не мешает.
Польза комментариев
Чтобы избежать недопонимания со стороны людей, незнакомых c bash, лучше дополнять первый (идиоматический) пример комментариями, объясняющими, что делает данный однострочник bash.
Да и вообще комменты в коде помогут читающему быстрее и легче понять этот код.
Команда help
Команда help
в bash дает краткие подсказки по встроенным функциям bash вместо объемного текста в man
.
help test # подсказки о выражениях проверки условий (-n и т.д.)
help [ # synonym for the "test" builtin ([...])
KISS
Keep It Simple, Stupid.
Сложность - враг безопасности. Сложность затрудняет чтение, понимание, отладку. Понятно, что при современных требованиях системы не могу быть простыми, но делать сложнее, чем требуется, не нужно.
Цитата 🐺 от Brian Kernighan:
Отладка в 2 раза сложнее написания кода, поэтому написав настолько сложный код, насколько вы способны, по определению будете недостаточно умны, чтобы отладить его.
Don’t reinvent the wheel
Не изобретай велосипед: большинство задуманных тобой вещей уже было сделано раньше.
+ как бы ты не старался, превзойти качество и надежность, например, rsync
не сможешь - юзай его просто.
Пиши DRY код/документацию
Don’t Repeat Yourself.
Несколько копий одного и того же кода обязательно рассинхронизируются (чисто вопрос времени).
Проверка доступности внешних утилит
Проверяй их доступность так:
[[ -x /path/2/tool ]] || { ...блок обработки ошибки... }
Соус: Книга “Идиомы Bash” → Глава 11. “Разработка своего руководства по стилю” → “Другие рекомендации”
Find out public IP
PUBLIC_IP="$(dig +short myip.opendns.com @resolver1.opendns.com)"
Создание временного файла с установкой ловушки для его удаления
WORD_FILE='/tmp/words.txt'
> "$WORD_FILE"
trap "rm -f $WORD_FILE" ABRT EXIT HUP INT QUIT TERM
Соус: Книга “Идиомы Bash” → Глава 7. “Списки и хеши” → “Пример подсчета слов” → “Пример 7.5”
Проверка, выполняется ли скрипт в интерактивном режиме
Иногда нужно знать, выполняется ли код в интерактивном режиме. Это может понадобится для изменения поведения при запросе ввода или для установки определенных конфиг-параметров интерактивной оболочки.
case "$-" in
*i*) echo 'This shell is interactive' ;;
*) echo 'This shell is not interactive' ;;
esac
# Советы, примечания, сценарии использования для bash
if [[ -z "$PS1" ]]; then
echo 'This shell is interactive'
else
echo 'This shell is not interactive'
fi
Также можно юзать проверку -t FD
, которая возвращает true, если дескриптор файлы открыт в терминале. Если дескриптор файла не указан, то возвращается 0
(STDIN
). Эту проверку можно объединить с другими тестами, например bash как оболочки:
# Только если bash выполняется в терминале!
if [ -t 1 -a -n "$BASH_VERSION" ]; then
echo 'bash in a terminal'
else
echo 'NOT bash in a terminal'
fi
# или
# Только для интерактивного режима bash в терминале!
if [ -t 1 -a -n "$PS1" -a -n "$BASH_VERSION" ]; then
echo 'Interactive bash in a terminal'
else
echo 'NOT interactive bash in a terminal'
fi
В двух примерах выше были использованы одинарные кавычки вместо двойных, чтобы код мог выполняться другими интерпретаторами (например, dash).
Соус: Книга “Идиомы Bash” → Глава 9. “Файлы и не только” → “Код выполняется в интерактивном режиме?”
Локальные переменные
Если нужна локальная область видимости вне функции, юзай локальные переменные.
Как пример, возьму $IFS
, ибо обычно ее изменение нежелательно, поэтому целесообразнее изменять ее для определенного фрагмента кода (вне функции). In this case, нужно просто добавить присваивание переменной перед командой, для которой и требудется это изменение:
IFS=':' read ...
Соус: Книга “Идиомы Bash” → Глава 10. “Помимо идиом: работа с bash” → “Локальные переменные”
Поиск в списке процессов
При поиске процессов с помощью grep
одна из процессов - это сам grep
, а он мешается в выводе. Есть идиома, помогающая удалить строку с grep из вывода.
Вместо уродливого и неэффективного:
ps auwx | grep 'proggy' | grep -v 'grep'
юзай идиому:
ps auwx | grep '[p]roggy'
Строка
[p]roggy
в списке процессов не мэтчится с RegEx/[p]roggy/
, которое означаетproggy
. Короче,[p]roggy != proggy
.
Можно кстати еще юзать pgrep -fl
или старую команду pidof
.
Соус: Книга “Идиомы Bash” → Глава 10. “Помимо идиом: работа с bash” → “Поиск в списке процессов”
Ротация старых файлов
Логирование и архивирование логов круто, конечно, но иногда нужно сделать “ротацию” или удаление старых файлов.
Есть много идиоматических способов сделать это, здесь будут основные примеры.
Вот старые идиомы:
# Неэффективно - для каждого файла вызывается новый экземпляр `rm`
find /path/to/files -name 'some-pattern' -a -mtime 5 -exec rm -f \{\}\;
# Более эффективный вариант, т.к. файлы группируются в пакеты (`xargs`),
# но возможны ошибки, елси в именах файлов есть пробелы
find /path/to/files -name 'some-pattern' -a -mtime 5 | xargs rm -f
# Этот вариант лишен предыдущих траблов, но выглядит сложно
find /path/to/files -name 'some-pattern' -a -mtime 5 -print0 \
| xargs -0 rm -f
Идиома получше:
find /path/to/files \( -type f -a -name 'some-pattern' -a -mtime 5 \) -delete
- Для теста юзай
-ls
или-print
вместо-delete
- Юзай
( )
для группировки (обрабатывать только те элементы, которые соответствуют всем критериям), но скобки нужно экранировать, чтобы избежать интерпретации командной оболочкой - Предполагается, что между параметрами в выражении используется оператор
-a
(and - и); но при необходимости можно также юзать-o
(or - или). Но лучше добавлять в код-a
, ибо явное лучше неявного.
Соус: Книга “Идиомы Bash” → Глава 10. “Помимо идиом: работа с bash” → “Ротация старых файлов”
Модульное тестирование в bash
Есть фреймворк модульного тестирования для bash скриптов (аналогичный фреймворкам JUnit, PyUnit и т.д.) → shunit2
Соус: Книга “Идиомы Bash” → Глава 10. “Помимо идиом: работа с bash” → “Модульное тестирование в bash”
Определение ОС
Примитивный пример определения операционной системы, на которой будет запущен этот код:
if type -t wevutil $> /dev/null; then
OS='MSWIN'
elif type -t scutil $> /dev/null; then
OS='macOS'
else
OS='Linux'
fi
echo "$OS"
Соус: Книга Bash и кибербезопасность → Глава 2. Основы работы с bash → Написание первого сценари
я: определение типа операционной системы → Пример 2.3. osdetect.sh