$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогCLI с интерактивом: как сделать команды “как приложение”, а не как утилиту

CLI с интерактивом: как сделать команды “как приложение”, а не как утилиту

$ sudo teach IT
·22 июля 2026 г.·10 мин·47
CLI с интерактивом: как сделать команды “как приложение”, а не как утилиту

Поясним, как добавлять подтверждения, прогресс, выбор вариантов, нормальную обработку Ctrl+C и понятный UX. Рассмотрим структуру команды и контракты аргументов.

Содержание
Что значит “команда как приложение”: UX и поведение, а не только интерфейсПредсказуемые режимы работыКонтракты аргументов вместо “а что вы имели в виду”Состояния и обработка отменыСтруктура команды: от модели действий до аргументовМодель сценария: шаги и переходыКонтракты аргументов: необходимые, опциональные, взаимоисключающиеИнтерактив: как добавлять подтверждения, не ломая автоматизацию“Подтвердить” ≠ “спросить любой ценой”Рекомендованные варианты подтвержденияРеализация подтверждения: состояние и отменаАвтоматические сценарии: --yes и “разумный дефолт”Прогресс: как показывать его правильно и не раздражатьПрогресс должен соответствовать длительности операцииТри уровня информативности прогрессаПрогресс и логирование: разделяйте каналыОтмена во время прогрессаВыбор вариантов: когда интерактив улучшает интерфейс, а не заменяет аргументыМеню вместо “введите сложное значение”Контракт выбора: значение, идентификатор и отображениеНормализация ввода и обработка ошибокНормальная обработка Ctrl+C: коды возврата и безопасность состоянияЧто должен делать CLI при Ctrl+CЧем опасен “просто поймать Ctrl+C и игнорировать”Отдельно: “Ctrl+C во время подтверждения”Практика: фазируйте отменуДетерминированность и режимы: интерактив должен отключаться там, где его нельзя включатьКогда интерактив недопустимРазрешайте интерактив как функцию окружения, а не “магии”Параметр “force/yes” должен быть последним словомКонтракты аргументов и командная спецификация: как не превратить CLI в хаосУтверждайте инварианты до исполненияВалидация до интерактиваПроработайте help и сообщения об ошибкахПример: проектирование команды с интерактивом и понятными ветками

Интерактивный CLI давно перестал быть нишей. Пользователи ожидают от команд не только “выполни действие и верни код возврата”, но и нормального UX: подтверждений до опасных операций, отображения прогресса, выбора вариантов вместо ручного ввода сложных параметров, корректной реакции на Ctrl+C, понятных ошибок и даже “сценариев” вместо линейных консольных логов.

Проблема в том, что многие CLI продолжают проектировать как утилиты: аргументы — это строка кода, а взаимодействие — “прочитайте README”. В результате команда ведёт себя непредсказуемо, при отмене оставляет систему в неопределённом состоянии, а вывод превращается в нечитаемую ленту текста.

Ниже — практический разбор того, как проектировать CLI с интерактивом так, чтобы он ощущался как приложение: с контрактами аргументов, управляемым сценарием выполнения и предсказуемыми состояниями.


Что значит “команда как приложение”: UX и поведение, а не только интерфейс

“Как приложение” в CLI обычно выражается в нескольких свойствах:

Предсказуемые режимы работы

Команда должна работать одинаково предсказуемо в:

  • неинтерактивном режиме (например, в CI): всё управляется аргументами/флагами, интерактив не включается;
  • интерактивном режиме (в терминале): пользователь получает вопросы/меню, подтверждения, прогресс.

Эта разница должна быть прозрачной. Частая ошибка — “команда иногда спрашивает, а иногда молчит”, причём условие скрыто (например, из-за наличия TTY).

Контракты аргументов вместо “а что вы имели в виду”

У команды должен быть явно определённый контракт:

  • что обязательно,
  • что опционально,
  • какие значения допустимы,
  • какие комбинации запрещены,
  • как разрешаются конфликты (приоритеты).

Интерактив не должен ломать контракт. Напротив: интерактив может заполнить пробелы, но не должен “магически” игнорировать указанные пользователем значения.

Состояния и обработка отмены

У приложения есть состояния (“проверяем…”, “готовим…”, “выполняем…”, “завершаем…”) и обработка отмены. В CLI то же самое: Ctrl+C не должен приводить к полуработе без подсказок. В идеале отмена должна быть “безопасной”:

  • либо откат до последнего шага,
  • либо явное предупреждение, что часть операций не отменяется.

Структура команды: от модели действий до аргументов

Чтобы CLI ощущался как приложение, начните проектирование не с флагов, а с модели выполнения.

Модель сценария: шаги и переходы

Сведите команду к “машине состояний”:

  1. Сбор входных данных
    Из аргументов + опционально из интерактива (если разрешён).
  2. Валидация
    Проверки формата, прав, зависимостей.
  3. Планирование
    Подготовка “что будет сделано” (полезно для подтверждения).
  4. Исполнение
    Реальные операции.
  5. Завершение
    Суммарный результат, ссылки на артефакты, советы следующего шага.

Такой подход упрощает и UX, и тестируемость: вы можете проверять отдельные шаги без запуска реальных действий.

Контракты аргументов: необходимые, опциональные, взаимоисключающие

Хороший CLI описывает правила на уровне интерфейса. Например:

  • --force / --yes — подтвердить опасные действия без вопросов.
  • --dry-run — показать план без изменений.
  • --format json — вывести машинно-читаемый результат (и тогда интерактив отключается).
  • --timeout 30s — ограничение ожидания.
  • --path / --remote — источники/цели.

Дальше правила:

  • Если --dry-run включён — --yes не нужен, но подтверждение может показывать, что изменения не выполнятся.
  • Если --format json — интерактив не включается: JSON должен быть детерминированным.
  • --watch несовместим с --timeout (например, потому что watch сам “живёт”).
  • --target обязателен, если выбран --mode deploy.

Это всё лучше фиксировать в коде как проверки до “исполнения”, а интерактив — использовать только для заполнения отсутствующих значений.


Интерактив: как добавлять подтверждения, не ломая автоматизацию

“Подтвердить” ≠ “спросить любой ценой”

Подтверждения нужны в двух случаях:

  1. Опасные действия (удаление, перезапись, миграции, изменение конфигурации).
  2. Неполная ясность (например, пользователь не указал цель или режим).

Но важно: подтверждение должно быть обусловлено риском и режимом запуска.

Пример UX:

  • в TTY и без --yes — спрашиваем;
  • в CI (нет TTY) и нет --yes — команда завершается с понятной ошибкой вида: “Требуется --yes для удаления”;
  • при --dry-run — подтверждение не требуется, но можно показать план.

Рекомендованные варианты подтверждения

Пользовательских “кнопок” обычно достаточно:

  • y/N (да по умолчанию только если безопасно)
  • yes/no для более явной верификации
  • ввод дополнительных параметров (“ввести ‘delete’ чтобы подтвердить” — для особо опасных операций)

Из практики: если команда действительно разрушительная, простого y может быть недостаточно. Но не перегибайте — иначе люди будут использовать --force всегда, и смысл подтверждений исчезнет.

Реализация подтверждения: состояние и отмена

Подтверждение — это часть сценария, поэтому оно должно:

  • корректно обрабатывать Ctrl+C (выход без продолжения),
  • возвращать согласованный результат (“подтверждено/отменено”),
  • быть консистентным в разных ветках.

Ниже пример для Python с использованием стандартного signal и input(); логика показана схематично, чтобы её можно было перенести на вашу платформу.

code
import sys
import signal

class Cancelled(Exception):
    pass

def install_sigint_handler():
    def handler(signum, frame):
        raise Cancelled()
    signal.signal(signal.SIGINT, handler)

def confirm(prompt: str, default_no: bool = True) -> bool:
    """
    Возвращает True если пользователь подтвердил, иначе False.
    """
    if default_no:
        suffix = "[y/N]"
    else:
        suffix = "[Y/n]"

    while True:
        try:
            answer = input(f"{prompt} {suffix} ").strip().lower()
        except EOFError:
            return False  # нечего подтверждать
        except Cancelled:
            # Важно: не делаем вид, что подтверждение было.
            raise

        if not answer:
            return not default_no  # если пусто — возвращаем дефолт
        if answer in {"y", "yes"}:
            return True
        if answer in {"n", "no"}:
            return False

        print("Пожалуйста, ответьте 'y' или 'n'.", file=sys.stderr)

def main():
    install_sigint_handler()
    # ... где-то в сценарии:
    dangerous = True

    try:
        if dangerous:
            ok = confirm("Вы уверены, что хотите удалить данные?", default_no=True)
            if not ok:
                print("Отменено пользователем.")
                return 1
        print("Действие выполнено.")
        return 0
    except Cancelled:
        print("\nОперация отменена (Ctrl+C).")
        return 130

if __name__ == "__main__":
    raise SystemExit(main())

Обратите внимание на два момента:

  • при Ctrl+C возвращаем код 130 — это распространённая договорённость Unix-подобных систем для “terminated by Ctrl+C”.
  • подтверждение не пытается “поймать и продолжить”: отмена должна прерывать сценарий.

Автоматические сценарии: --yes и “разумный дефолт”

Если вы вводите интерактив, нужны режимы, которые гарантируют детерминированность:

  • --yes (или --no-interaction) чтобы в автоматизации не происходило вопросов;
  • понятная ошибка, если пользователь запустил интерактивную ветку в неинтерактивном окружении.

Критически важно: код возврата должен соответствовать причине:

  • 0 — успешно,
  • 1 — пользователь отменил или невалидный ввод (иногда выделяют разные коды),
  • 2 — синтаксическая ошибка аргументов,
  • 130 — Ctrl+C.

Прогресс: как показывать его правильно и не раздражать

Прогресс должен соответствовать длительности операции

Прогресс полезен только когда:

  • операция реально занимает время,
  • есть этапы или хотя бы можно оценить завершённость.

Если вы показываете “спиннер” без содержания — это скорее вред. Пользователь видит “что-то происходит”, но не понимает “что именно” и “сколько осталось”.

Три уровня информативности прогресса

  1. Этапы: “Сканируем…”, “Проверяем доступ…”, “Загружаем…”
  2. Прогресс по итерациям: индикатор “12/200 файлов”.
  3. Процент: только если можно оценить общее количество/объём.

В хороших CLI обычно комбинация: сначала показывают текущий этап, а затем добавляют процент/счётчик.

Прогресс и логирование: разделяйте каналы

Одна из частых проблем — прогресс “пишет” в stdout, а ошибки — туда же. В результате:

  • в пайпе/redirect теряется читаемость,
  • JSON-вывод смешивается со строками прогресса.

Решение: прогресс — в stdout только если формат не машинный, а ошибки — в stderr. Многие библиотеки (например, tqdm в Python) имеют параметры, куда писать. В любом случае закрепите правило в дизайне.

Отмена во время прогресса

Во время длительной операции важно:

  • корректно реагировать на Ctrl+C,
  • завершать текущий шаг безопасно,
  • при необходимости делать “частичный rollback”.

На практике часто выбирают компромисс: прерывают выполнение и пишут итог:

  • “прервано на шаге X; частично выполнено: Y; пропущено: Z”.

Это намного лучше, чем молча оставить следы.


Выбор вариантов: когда интерактив улучшает интерфейс, а не заменяет аргументы

Меню вместо “введите сложное значение”

Интерактив удобен там, где пользователь:

  • не знает точных значений (например, --profile prod-eu-west),
  • хочет выбрать из существующих (список проектов/контейнеров/шаблонов),
  • совершает действие один раз, а не в массовом режиме.

В таком случае команда может:

  • показать список,
  • дать поиск,
  • подсветить последствия выбора (“будут затронуты…”, “используется…”).

Но при этом команда должна поддерживать прямые аргументы, если пользователь знает, что делает.

Контракт выбора: значение, идентификатор и отображение

Важно различать:

  • идентификатор (что реально нужно для выполнения),
  • отображаемая метка (что видит пользователь).

Пример: выбор “Project: Payments” в меню должен приводить к внутреннему --project-id=123. Тогда при изменении названия “Payments” сценарий не ломается.

Нормализация ввода и обработка ошибок

Меню должно уметь:

  • принимать ввод “число”,
  • принимать “ключевые слова”,
  • повторять вопрос при ошибке,
  • отменяться корректно по Ctrl+C.

Типичная ошибка — позволить пользователю ввести свободный текст, не проверив его на допустимость, а потом “падать” на позже.

Если вы строите UX как приложение, то проверка должна происходить до исполнения.


Нормальная обработка Ctrl+C: коды возврата и безопасность состояния

Что должен делать CLI при Ctrl+C

Минимальные ожидания:

  1. Остановить текущий шаг.
  2. Не продолжать выполнение “в фоне”.
  3. Дать пользователю короткое сообщение.
  4. Возвратить корректный exit code (обычно 130).

Если операция оставляет следы — сообщить:

  • что сделано,
  • что не сделано,
  • что можно безопасно повторить,
  • что требует ручного вмешательства.

Чем опасен “просто поймать Ctrl+C и игнорировать”

Если вы перехватили SIGINT, но не остановили исполняющую часть, вы создаёте иллюзию отмены. Пользователь повторяет команду, а старая операция продолжает изменять систему.

Правило: если отмена подтверждена сигналом — сценарий должен прекращаться детерминированно.

Отдельно: “Ctrl+C во время подтверждения”

Если Ctrl+C пришёл в момент, когда команда спрашивает “удалить?” — это не “ошибка”, а отмена. Сообщение можно сделать нейтральным:

  • “Отменено пользователем.”
  • exit code — 130.

Практика: фазируйте отмену

Обычно помогают два уровня:

  • на “планировании/валидации” отмена просто прекращает сценарий;
  • на “исполнении” отмена либо прерывает шаг, либо переводит операцию в безопасный режим завершения.

Детерминированность и режимы: интерактив должен отключаться там, где его нельзя включать

Когда интерактив недопустим

  • stdin/tty отсутствует (например, в пайпе или в CI).
  • пользователь выбрал --format json или --machine.
  • вывод используется как вход в другой инструмент.
  • команда работает как часть пайплайна.

В этих случаях правильное поведение — не “угадывать”, а требовать явные параметры. Вместо “мы что-то спросили, но input недоступен” — сообщение о том, что нужно добавить флаг.

Пример текста ошибки:

  • “Команда требует подтверждения. Запустите с --yes или --dry-run, либо укажите --force.”

Разрешайте интерактив как функцию окружения, а не “магии”

Некоторые CLI включают интерактив только при наличии TTY. Это нормальная эвристика, но важно:

  • явно документировать это в help,
  • обеспечить предсказуемость (не менять поведение внезапно при разных терминалах).

Параметр “force/yes” должен быть последним словом

Пользователь явно указал --yes — значит вопросов не будет. Это и есть “контракт”.


Контракты аргументов и командная спецификация: как не превратить CLI в хаос

Один из самых недооценённых аспектов — “контракт” не как документ, а как механика.

Утверждайте инварианты до исполнения

Проверяйте заранее:

  • что комбинации аргументов допустимы,
  • что путь/ресурс существует (или создаётся),
  • что в режиме --dry-run не происходит побочных эффектов,
  • что подтверждения выполняются только для определённых операций.

Валидация до интерактива

Если аргументы заведомо противоречат друг другу — интерактив не должен вступать в игру. Например:

  • --mode delete с --target без указания обязательных параметров — это можно уточнить интерактивом;
  • --format json с необходимостью интерактива — это должно быть ошибкой, потому что формат требует детерминированности.

Проработайте help и сообщения об ошибках

Помните, что пользователь часто читает не README, а “ошибку из CLI”. Поэтому:

  • сообщение должно говорить “что не так” и “что сделать”,
  • не должно быть внутреннего стектрейса (кроме debug-режима).

Пример: проектирование команды с интерактивом и понятными ветками

Рассмотрим сценарий: cli delete — удаление набора ресурсов. У него есть:

  • обязательный --resource (если не задан — запросить),
  • подтверждение перед удалением (если не задан --yes),
  • прогресс удаления,
  • корректная отмена.

Ниже — “каркас”, который демонстрирует общий паттерн. Это не привязано к конкретной библиотеке, но отражает архитектуру.

code
import sys
import time
import signal
from dataclasses import dataclass
from typing import Optional, List

class Cancelled(Exception):
    pass

def install_sigint_handler():
    def handler(signum, frame):
        raise Cancelled()
    signal.signal(signal.SIGINT, handler)

@dataclass
class DeleteOptions:
    resource: Optional[str]
    yes: bool
    dry_run: bool
    interactive_allowed: bool

def parse_args(argv: List[str]) -> DeleteOptions:
    """
    Здесь упрощённый парсер. В реальном проекте используйте argparse/typer/click и т.п.
    """
    # Псевдо-парсинг для иллюстрации:
    resource = None
    yes = False
    dry_run = False

    for a in argv:
        if a.startswith("--resource="):
            resource = a.split("=", 1)[1]
        elif a == "--yes":
            yes = True
        elif a == "--dry-run":
            dry_run = True

    interactive_allowed = sys.stdin.isatty() and sys.stdout.isatty()
    return DeleteOptions(resource=resource, yes=yes, dry_run=dry_run, interactive_allowed=interactive_allowed)

def prompt_resource() -> str:
    while True:
        try:
            s = input

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Приложения для iPhone и Apple Watch на SwiftUI

Приложения для iPhone и Apple Watch на SwiftUI

Разработка приложений для iPhone и Apple Watch на SwiftUI: навигация, SwiftData, виджеты, часы, выпуск. Нужен Mac с Xcode 27, сами устройства не нужны.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Приложения для macOS на SwiftUI

Приложения для macOS на SwiftUI

Разработка приложений для Mac на SwiftUI: окна и меню, Liquid Glass, SwiftData, сеть, выпуск. Нужен Mac с macOS 27 и Xcode 27.

Перейти к курсу

Другие статьи

Пишем CLI для разработчика на Python: команды, конфиги и корректные exit codes по стандарту проекта
пишем

Пишем CLI для разработчика на Python: команды, конфиги и корректные exit codes по стандарту проекта

Построим удобный интерфейс командной утилиты: структура подкоманд, чтение конфигов, валидация входных параметров и единая логика ошибок с понятными exit code. Сфокусируемся на UX для тех, кто запускает CLI в CI и в скриптах.

15 сентября 2026 г.
1010
Docker для разработчика: запускаем приложение в контейнере с нуля
docker

Docker для разработчика: запускаем приложение в контейнере с нуля

Пошаговый гид по Docker без лишней теории: пишем Dockerfile, поднимаем сервис, разбираемся с volumes и сетями. После прочтения вы сможете контейнеризировать любой свой проект.

17 июля 2026 г.
690
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1421
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1070
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
620

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Что значит “команда как приложение”: UX и поведение, а не только интерфейсПредсказуемые режимы работыКонтракты аргументов вместо “а что вы имели в виду”Состояния и обработка отменыСтруктура команды: от модели действий до аргументовМодель сценария: шаги и переходыКонтракты аргументов: необходимые, опциональные, взаимоисключающиеИнтерактив: как добавлять подтверждения, не ломая автоматизацию“Подтвердить” ≠ “спросить любой ценой”Рекомендованные варианты подтвержденияРеализация подтверждения: состояние и отменаАвтоматические сценарии: --yes и “разумный дефолт”Прогресс: как показывать его правильно и не раздражатьПрогресс должен соответствовать длительности операцииТри уровня информативности прогрессаПрогресс и логирование: разделяйте каналыОтмена во время прогрессаВыбор вариантов: когда интерактив улучшает интерфейс, а не заменяет аргументыМеню вместо “введите сложное значение”Контракт выбора: значение, идентификатор и отображениеНормализация ввода и обработка ошибокНормальная обработка Ctrl+C: коды возврата и безопасность состоянияЧто должен делать CLI при Ctrl+CЧем опасен “просто поймать Ctrl+C и игнорировать”Отдельно: “Ctrl+C во время подтверждения”Практика: фазируйте отменуДетерминированность и режимы: интерактив должен отключаться там, где его нельзя включатьКогда интерактив недопустимРазрешайте интерактив как функцию окружения, а не “магии”Параметр “force/yes” должен быть последним словомКонтракты аргументов и командная спецификация: как не превратить CLI в хаосУтверждайте инварианты до исполненияВалидация до интерактиваПроработайте help и сообщения об ошибкахПример: проектирование команды с интерактивом и понятными ветками