“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