Обязательно посмотрите shell-скрипт в репозитории — он чудо как хорош. Раскрашивает выходные данные, надёжный как скала… настоящий мастер-класс по созданию shell-скриптов.
Спасибо Гуннару Морлингу за добрые слова!

В январе 2024 года меня, вместе с несколькими дюжинами других гиков, повёрнутых на производительности, что называется, «заснайпили», заинтересовав участием в конкурсе Гуннара One Billion Row Challenge (1BRC).
Гуннара, оценивающего результаты работы конкурсантов (бесплатно), очень быстро буквально завалил непрерывный поток решений. Я рискнул помочь ему автоматизировать оценку работ и написал Shell‑скрипт, за что и получил от него вышеприведённый хвалебный отзыв. Он это сказал в выступлении на конференции JavaZone (# 1BRC‑Nerd Sniping the Java Community — Gunnar Morling). Посмотрите это выступление, если хотите узнать о подходах к повышению производительности кода, применённых на конкурсе.
Здесь я расскажу о 6 приёмах, которые я использовал в Shell-скрипте для конкурса. Эти приёмы позволили сделать скрипт надёжным, безопасным и приятным в использовании для Гуннара.
1. Всеобъемлющая обработка ошибок и проверка входных данных
Я уверен в том, что чёткие и понятные сообщения об ошибках — это краеугольный камень хорошего UX. Именно поэтому я и реализовал в скрипте качественные механизмы, обеспечивающие обработку ошибок и проверку ввода. Например:
if [ -z "$1" ]
then
echo "Usage: evaluate.sh <fork name> (<fork name 2> ...)"
echo " for each fork, there must be a 'calculate_average_<fork name>.sh' script and an optional 'prepare_<fork name>.sh'."
exit 1
fi
Применение такого подхода помогает пользователям быстро выявлять и устранять проблемы, не тратя на это лишние время и нервы.
2. Понятные выходные данные, оформляемые с использованием разных цветов
Для того чтобы пользователю было бы удобнее и приятнее читать данные, которые выводит скрипт, я использовал ANSI-коды цветов, выделяя важную информацию, тексты предупреждений и ошибок. Например:
BOLD_RED='\033[1;31m'
RESET='\033[0m'
echo -e "${BOLD_RED}ERROR${RESET}: ./calculate_average_$fork.sh does not exist." >&2
Визуальные различия между текстами помогают пользователю быстро уловить характер сообщения.
3. Подробные сведения о ходе работы скрипта
Мне хотелось, чтобы пользователи точно знали о том, чем именно скрипт занимается на каждом этапе его работы. Для того чтобы этого достичь, я реализовал функцию, которая, перед выполнением команд, выводила сведения о них.
function print_and_execute() {
echo "+ $@" >&2
"$@"
}
Это напоминает то, как работает встроенная опция Bash set -x
, которая позволяет включить трассировку выполнения скрипта. Но мой подход даёт автору скрипта возможность более точно управлять тем, что именно будет выводиться.
Такой уровень прозрачности программы не только снабжает пользователя сведениями о том, что в ней происходит, но и, в том случае, если что-то пойдёт не так, помогает в отладке.
4. Стратегический подход к обработке ошибок с помощью set -e и set +e
Мне хотелось сделать так, чтобы скрипт немедленно завершал бы работу в том случае, если в нём самом произошла бы ошибка. Но при этом я стремился к тому, чтобы позволить ему продолжить работу в том случае, если проблемы возникали в одном из форков. Для того чтобы этого достичь, я стратегически разместил в нужных местах скрипта опции Bash set -e
и set +e
. Вот как я это сделал:
# В начале скрипта
set -eo pipefail
# Перед запуском тестов и бенчмарков для каждого из форков
for fork in "$@"; do
set +e # нам не нужно, чтобы prepare.sh, test.sh или hyperfine, давшие сбой на 1 форке, преждевременно останавливали бы скрипт
# Запуск подготовительного скрипта (упрощено)
print_and_execute source "./prepare_$fork.sh"
# Запуск набора тестов (упрощено)
print_and_execute $TIMEOUT ./test.sh $fork
# ... (другие операции, специфичные для форка)
done
set -e # Снова включить возможность выхода при возникновении ошибки, сделав это после выполнения операций, специфичных для форка
Такой подход даёт автору скрипта полный контроль над тем, какие ошибки приводят к остановке скрипта, а какие могут быть обработаны другими способами.
5. Адаптация скрипта к различным платформам
Зная о том, что пользователи могут запускать этот скрипт из разных операционных систем, я добавил в него логику, позволяющую определить используемую ОС и соответствующим образом откорректировать поведение скрипта:
if [ "$(uname -s)" == "Linux" ]; then
TIMEOUT="timeout -v $RUN_TIME_LIMIT"
else # Исходим из предположения о том, что это — MacOS
if [ -x "$(command -v gtimeout)" ]; then
TIMEOUT="gtimeout -v $RUN_TIME_LIMIT"
else
echo -e "${BOLD_YELLOW}WARNING${RESET} gtimeout not available, install with `brew install coreutils` or benchmark runs may take indefinitely long."
fi
fi
Благодаря этому приёму пользователи, работающие в разных окружениях, оказываются в равных условиях. Так, например, многие участники конкурса #1BRC программировали на MacOS, а оценочный сервер работал под Linux.
6. Использование временных меток при сохранении в файлах результатов множества вызовов скрипта
Для того, чтобы при многократных запусках скрипта не перезаписывались бы результаты его работы, полученные ранее, я реализовал систему формирования выходных файлов с использованием временных меток. Это позволяет пользователям запускать скрипт по много раз и не терять при этом историю результатов. Вот как я это сделал:
filetimestamp=$(date +"%Y%m%d%H%M%S")
# ... (в цикле, для каждого из форков)
HYPERFINE_OPTS="--warmup 0 --runs $RUNS --export-json $fork-$filetimestamp-timing.json --output ./$fork-$filetimestamp.out"
# ... (после работы бенчмарков)
echo "Raw results saved to file(s):"
for fork in "$@"; do
if [ -f "$fork-$filetimestamp-timing.json" ]; then
cat $fork-$filetimestamp-timing.json >> $fork-$filetimestamp.out
rm $fork-$filetimestamp-timing.json
fi
if [ -f "$fork-$filetimestamp.out" ]; then
echo " $fork-$filetimestamp.out"
fi
done
Итоги
Вот — полный код скрипта evaluate.sh
, используемого для оценки заданий участников конкурса #1BRC, хранящийся в его репозитории.
Применяя вышеописанные приёмы, я стремился к созданию надёжного и информативного Shell-скрипта, с которым приятно работать, который сослужит хорошую службу пользователям, запускающим и анализирующим бенчмарки. Надеюсь, мои идеи вдохновят вас на улучшение UX ваших собственных Shell-скриптов. Здесь и здесь вы можете присоединиться к обсуждению этой статьи и поделиться своими мыслями о создании качественных Shell-скриптов.
Дополнение от 19 марта 2025 года: после того, как читатели обсудили эту статью, я написал продолжение, в котором приведены 6 приёмов написания Shell-скриптов от пользователей Hacker News.
О, а приходите к нам работать? ? ?
Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.
Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.
Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.
RumataEstora
Сомнительно. Чем вам пара
set -x
/set +x
не угодила? И название длинное.Конечно же будет выводится лишняя отладочная строка
+ set +x
. Но это мелочи. Зато можно так писать, а отладку включать/выключать комметированием первой строки: