Привет, Хабр!
Сегодня рассмотрим, как на базе Bash собрать свой собственный кастомный шелл — с автодополнением, историей, алиасами, логами, цветным prompt’ом, подсказками по sudo и возможностью расширения.
Минимальный REPL-интерпретатор на Bash
Начнём с базовой конструкции, которая делает из bash-а простой цикл чтения и выполнения команд:
#!/usr/bin/env bash HISTORY_FILE="$HOME/.mybash_history" touch "$HISTORY_FILE" trap "echo; exit 0" SIGINT SIGTERM while true; do read -e -p "→ " CMD echo "$CMD" >> "$HISTORY_FILE" eval "$CMD" done
HISTORY_FILE — файл для сохранения истории между сессиями, trap — ловим Ctrl+C и красиво выходим, read -e — включает поддержку стрелок и автодополнения, eval "$CMD" — исполняем введённую строку как Bash-команду.
Поддержка алиасов
Добавим свои алиасы и включим их поддержку:
shopt -s expand_aliases alias ll='ls -la' alias gs='git status'
shopt -s expand_aliases — без него alias’ы в скрипте не работают. Дальше можно объявлять любые свои сокращения.
Добавим логирование команд
Хотим знать, кто и когда запускал какую команду:
LOGFILE="$HOME/.mybash_cmd.log" log_command() { echo "$(date "+%Y-%m-%d %H:%M:%S") | $1" >> "$LOGFILE" }
log_command — простая функция, логирующая команду с временной меткой.
Используем её в цикле:
read -e -p "→ " CMD log_command "$CMD" eval "$CMD"
Измерение времени выполнения команды
Вариант с миллисекундами:
start=$(date +%s%3N) eval "$CMD" end=$(date +%s%3N) echo "Команда выполнена за $((end - start)) мс"
date +%s%3N — время в миллисекундах. Считаем разницу до и после выполнения команды.
Подсказка на sudo при ошибке доступа
if eval "$CMD" 2>&1 | grep -iq "permission denied\|operation not permitted"; then echo "Возможно, стоит попробовать: sudo $CMD" fi
2>&1 — захватываем stderr. grep -iq — проверяем сообщение об ошибке доступа, не учитывая регистр.
Цветной prompt
Пример синим цветом:
read -e -p $'\e[1;34m→\e[0m ' CMD
\e[1;34m — включаем синий цвет. \e[0m — сбрасываем в стандартный после символа prompt-а.
Лог piped-команд
if [[ "$CMD" == *"|"* ]]; then echo "PIPE: $CMD" >> ~/.mybash_pipe.log fi
Простая проверка на наличие pipe в команде и логирование её отдельно.
Используем PROMPT_COMMAND для хуков
export PROMPT_COMMAND='echo "[Hook] Снова в prompt-е"'
PROMPT_COMMAND — переменная, в которую можно вписать команду, исполняемую до показа prompt’а. Подходит для логов, счётчиков, метрик и вообще чего угодно.
Собираем всё воедино — финальный скрипт
#!/usr/bin/env bash shopt -s expand_aliases alias ll='ls -la' alias gs='git status' HISTORY_FILE="$HOME/.mybash_history" LOGFILE="$HOME/.mybash_cmd.log" PIPELOG="$HOME/.mybash_pipe.log" touch "$HISTORY_FILE" "$LOGFILE" "$PIPELOG" trap "echo; exit 0" SIGINT SIGTERM log_command() { echo "$(date "+%Y-%m-%d %H:%M:%S") | $1" >> "$LOGFILE" } while true; do read -e -p $'\e[1;34m→\e[0m ' CMD echo "$CMD" >> "$HISTORY_FILE" log_command "$CMD" if [[ "$CMD" == *"|"* ]]; then echo "PIPE: $CMD" >> "$PIPELOG" fi start=$(date +%s%3N) if ! eval "$CMD" 2> >(tee /tmp/mybash_err.log >&2); then if grep -iq "permission denied\|operation not permitted" /tmp/mybash_err.log; then echo "Возможно, стоит попробовать: sudo $CMD" fi fi end=$(date +%s%3N) echo "Команда выполнена за $((end - start)) мс" done
Это уже вполне себе рабочая мини-оболочка, которая аккуратно собирает всё, что мы настроили раньше: автодополнение через read -e, история команд, которая не исчезает между сессиями, alias’ы как в нормальном shell’е, логирование всего подряд (включая пайпы), замеры времени выполнения и подсказки на тему «а не забыли ли вы sudo?». Всё это живёт в бесконечном цикле, превращая обычный bash-процесс в кастомизированный REPL, который реагирует на команды и делает это чуть умнее, чем дефолтный bash.
Что можно докрутить
Интеграция с gh, kubectl, helm и прочими DevOps-инструментами
Современные DevOps-интерфейсы — это, по сути, API-обёртки, которые отлично дружат с Bash. Можно превратить шелл в DevX-инструмент, просто завернув часто используемые вызовы в alias или функцию.
Примеры:
alias pr='gh pr list --limit 10' alias logs='kubectl logs -f $(kubectl get pods | fzf)' alias helmstatus='helm list --all-namespaces'
Или завести функции с аргументами:
ghissue() { gh issue list --label "$1" --limit 5 }
Добавляем это в начало скрипта или подгружай через отдельный конфиг.
Поддержка JSON-вывода и jq
Почти все современные CLI-утилиты (docker, gh, kubectl, aws, gcloud) отдают данные в JSON. А jq — это grep/awk для JSON.
Примеры alias:
alias pods='kubectl get pods -o json | jq ".items[].metadata.name"' alias ghactions='gh api repos/:owner/:repo/actions/runs | jq ".workflow_runs[].status"' alias docker_ports='docker inspect $(docker ps -q) | jq \".. | .HostPort? // empty\"'
Можно даже динамически строить меню с select, fzf, gum, whiptail.
Запуск в Docker (изолированная среда + CI/CD)
Хочешь свой REPL внутри контейнера — для обучения, деплой-скриптов или как среду в CI?
Создай Dockerfile:
FROM bash:5.2 COPY mybash.sh /mybash.sh RUN chmod +x /mybash.sh CMD [\"/mybash.sh\"]
Собери и запусти:
docker build -t my-bash-repl . docker run -it my-bash-repl
Теперь есть shell-движок в изолированной капсуле.
А вам приходилось делать что-то подобное? Делитесь в комментариях
Статья подготовлена в преддверии старта специализации «Administrator Linux». На странице специализации можно ознакомиться с подробной программой, а также посмотреть записи открытых уроков.
А в календаре мероприятий уже доступно расписание всех открытых уроков.
ссылка на оригинал статьи https://habr.com/ru/articles/898508/
Добавить комментарий