2 ポイント 投稿者 GN⁺ 2024-02-07 | 1件のコメント | WhatsAppで共有
  • このガイドは、伝統的な UNIX の原則を現代的に更新し、人が使いやすく、自動化にも堅牢な CLI を作るためのオープンソース設計指針である
  • 優れた CLI は 人間優先で動作しつつ、stdout/stderr、終了コード、パイプ、JSON といった慣例を守り、ほかのプログラムと自然に組み合わせられるべきである
  • ヘルプ、ドキュメント、エラーメッセージ、出力は、ユーザーが次に取るべき行動を分かるようにする必要があり、--help、例、提案、進行状況表示、状態の説明が重要な役割を担う
  • 引数・フラグ・インタラクション・設定・環境変数は、スクリプト利用とターミナル利用の両方を考慮すべきであり、秘密値はフラグや 環境変数で直接受け取らないほうが安全である
  • CLI の名前、配布、分析データの収集まで、ユーザーのコントロール感と長期的な互換性を損なってはならず、慣例を破る場合にも目的が明確であるべきである

CLI 設計の基本哲学

  • このガイドは オープンソース 文書であり、伝統的な UNIX の原則を基盤に、現代的なコマンドラインプログラムの設計原則と具体的な指針を提供する
  • かつてのコマンドラインは、スクリプティングプラットフォーム上の REPL に近い 機械優先の環境だったが、今日の CLI は複数のツール・システム・プラットフォームにアクセスするテキストベース UI として、人間優先の性格が強い
  • コマンドラインには古い制約や独特の慣習があるものの、ほぼすべてのノート PC で利用でき、対話的な利用と自動化の両方が可能で、ほかのシステム構成要素より変化の速度が遅い
  • このガイドは emacs や vim のようなフルスクリーンのターミナルプログラムは扱わず、特定のプログラミング言語やツールチェーンにも依存しない

人間優先でありながら組み合わせ可能な CLI

  • CLI が主に人間が使うツールであるなら、人間を先に考えるべきである
    • 多くの CLI プログラムは人間のユーザーを対象にしているにもかかわらず、過去の機械中心のインタラクション設計をそのまま維持している
  • 小さなプログラムがきれいなインターフェースで連携して動作する UNIX 哲学は、今なお重要である
    • 標準入力・出力・エラー、シグナル、終了コードといった慣例が、プログラム間の組み合わせを可能にする
    • 行単位のプレーンテキストはパイプで渡しやすく、JSON はより構造化されたデータが必要なときに有用である
  • 一貫性は、CLI の学習コストを長期的な効率へと変える鍵である
    • 既存のパターンに従えば、ユーザーはコマンドやオプションをより簡単に推測できる
    • 慣例が使いやすさを損なう場合は、慎重に破ってもよい
  • 優れた CLI は無口すぎたり出力が多すぎたりせず、ユーザーが必要なだけの情報を得られるようにすべきである
  • CLI は GUI より発見性が低いと見なされがちだが、包括的なヘルプ、例、次のコマンドの提案、エラー時の対処案内によって学習しやすさを高められる

対話的インタラクションと堅牢性

  • コマンドラインの利用は、1 回の実行よりも、複数回の試行と修正へと続く 対話に近い
    • 誤った入力には、可能な修正案を提示できる
    • 複数段階の作業では中間状態を明確に示せる
    • 危険な作業の前に確認を求められる
  • CLI は実際に堅牢であるべきであり、同時にユーザーが堅牢だと感じられる必要がある
    • 予期しない入力を優雅に処理すべきである
    • 可能な場合、作業は冪等であるべきである
    • 恐ろしく見えるスタックトレースをデフォルト出力として表示すべきではない
  • ユーザーにソフトウェアが自分の味方だと感じさせる 共感が重要である
    • 絵文字を使ったりゲームのようにしたりすることではなく、ユーザーが成功できるよう細やかに設計することが核心である
  • ターミナルのエコシステムには不一致や混乱が多いが、制約が少ないおかげで新しい発明が可能だった
    • 既存のパターンには従いつつ、生産性やユーザー満足を損なう標準は意図的に捨ててもよい

必ず守るべき基本ルール

  • 可能な場合は コマンドライン引数パースライブラリを使うべきである
  • 成功時の終了コードは 0、失敗時には 0 以外の値を返すべきである
    • スクリプトは終了コードを通じてプログラムの成功・失敗を判断する
  • コマンドの主な出力は stdout に送るべきである
    • 機械可読な出力も基本的に stdout に送らなければ、パイプが正常に動作しない
  • ログメッセージやエラーなど、ユーザーに表示するメッセージは stderr に送るべきである
    • パイプで接続したとき、メッセージが次のコマンドの入力に混ざらない

ヘルプ設計

  • -h--help が渡されたら、十分なヘルプを表示すべきである
    • サブコマンドも独自のヘルプを持てる
    • -h を別の意味でオーバーロードすべきではない
  • 引数が必要なコマンドを引数なしで実行した場合は、簡潔なヘルプを表示すべきである
    • プログラムの説明
    • 1〜2 個の呼び出し例
    • フラグの説明
    • より詳しい情報は --help を使うよう案内する
  • ヘルプにはサポートへの経路を含めるとよい
    • ウェブサイトや GitHub リンクが一般的である
  • ウェブドキュメントがあるなら、ヘルプからそのドキュメントへリンクすべきである
    • サブコマンドに特定のページやアンカーがあるなら、直接リンクすると有用である
  • ユーザーはドキュメントより例を先に活用する傾向があるため、ヘルプでは を前のほうに配置するとよい
    • 複雑だが一般的なユースケースを先に示せる
    • 例が多い場合は、別のチートシート用コマンドやウェブページに置くのが適切である
  • ヘルプはスキャンしやすいようにフォーマットできる
    • 太字の見出しは可読性を高める
    • 端末非依存の方法で処理し、エスケープ文字がそのまま見えないようにすべきである
  • ユーザーが誤って入力し、意図を推測できる場合は提案すべきである
    • brew update jqbrew upgrade jq を案内するように動作できる
    • 提案されたコマンドを実行するか尋ねることはできるが、強制実行は避けるべきである
  • stdin から入力を受け取る必要があるコマンドで、stdin が対話型ターミナルなら、すぐにヘルプを表示して終了するか、stderr にメッセージを出力すべきである

ドキュメント化

  • ヘルプは即時の要約と一般的な作業の案内を提供し、ドキュメントはツールの目的・目的外のこと・動作方式・全体的な使い方を詳しく扱う
  • ウェブベースのドキュメントを提供すべきである
    • 検索でき、特定の箇所へリンクできる
    • ウェブドキュメントは最も包括的なドキュメント形式である
  • ターミナルベースのドキュメントも提供すべきである
    • すばやくアクセスできる
    • インストール済みツールのバージョンと同期される
    • インターネットなしでも動作する
  • man ページの提供を検討できる
    • 多くのユーザーはまず man mycmd を確認する
    • gitnpm のように、help サブコマンドを通じて man ページにアクセスできる

出力の原則

  • 人間が読みやすい出力が最も重要
    • 特定の出力ストリームが人間向けかどうかを判断する簡単な基準は、TTY かどうかである
  • 使いやすさを損なわない範囲で、機械が読み取れる出力を提供すべき
    • プレーンテキストの行ストリームは UNIX の普遍的なインターフェースである
    • ユーザーは出力結果を grep のようなツールに渡して、期待どおりに動作することを想定している
  • 人間に優しい出力が機械に優しい出力を壊す場合は、--plain を提供できる
    • 表形式の出力でセルを複数行に分けると、1レコード1行という期待を壊す可能性がある
    • --plain はスクリプト向けに、加工のない表形式の出力を提供する
  • --json が渡されたら、整形された JSON を出力すべき
    • JSON は複雑なデータ構造を扱いやすく、jq や多くの JSON CLI ツールと併用できる
    • Web サービスと curl を通じて直接パイプでつなぐ用途にも適している
  • 成功時には出力するが、短く保つべき
    • 伝統的な UNIX コマンドは問題がなければ何も出力しないことが多いが、人間には止まっているように見える場合がある
    • スクリプト向けに無出力が必要なら、-q オプションで不要な出力を隠せる
  • 状態を変更するコマンドは、何が変わったのかをユーザーに知らせるべき
    • git push は、実行中の作業とリモートブランチの新しい状態を示す例である
  • システムの現在状態を簡単に確認できるべき
    • git status はリポジトリの状態と、次に実行できるコマンドのヒントをあわせて表示する
  • プログラム内部世界の境界を越える操作は、通常は明示的であるべき
    • ユーザーが引数で渡していないファイルを読み書きする場合
    • リモートサーバーと通信してファイルをダウンロードする場合
  • 色は意図を持って使うべき
    • 使いすぎると意味が薄れ、読みにくくなる
    • TTY でない、NO_COLOR が設定されている、TERM=dumb である、または --no-color が渡された場合は、色を無効にすべき
  • stdout が対話型ターミナルでない場合は、アニメーションを出力すべきでない
    • CI ログで進捗表示が乱雑になるのを防げる
  • 大量のテキストを出力するときは、less のようなページャーを使える
    • stdin または stdout が対話型ターミナルのときだけ使うのが望ましい
    • less -FIRX は妥当なオプションセットとして使える

エラーメッセージ

  • エラーをドキュメントのように作れば、ユーザーがドキュメントを探す時間を減らせる
  • 予測可能なエラーは捕捉し、人間が理解できるように書き直すべき
    • 例: file.txt に書き込めず、chmod +w file.txt が必要かもしれない、といった案内
  • 信号対雑音比が重要
    • 関係のない出力が多いほど、ユーザーは問題を把握しにくくなる
    • 同じ種類のエラーが複数あるなら、1つの説明ヘッダーの下にまとめられる
  • 最も重要な情報は出力の最後に置くのがよい
    • ユーザーの視線は赤い文字に引き寄せられるため、意図的に控えめに使うべき
  • 予期しない、または説明しにくいエラーなら、デバッグ・トレースバック情報とバグ報告の方法を提供すべき
    • ユーザーを圧倒しないよう、デバッグログをターミナルではなくファイルに書ける
  • バグ報告は簡単にできるようにすべき
    • 可能な情報をあらかじめ入力した URL を提供する方法が例である

引数とフラグ

  • 引数は位置ベースのパラメーターで、フラグは名前付きのパラメーターである
    • cp foo bar におけるファイルパスは引数である
    • -r--recursive--file foo.txt はフラグである
  • 可能な場合は引数よりもフラグを優先すべき
    • 入力量は増えるが、意味が明確になる
    • 将来、入力方式を変更しやすい
  • すべてのフラグには長い名前を提供すべき
    • -h--help を併用するような形である
    • スクリプトで意味を明確に示すのに役立つ
  • 1文字のフラグは、よく使うオプションにのみ使うべき
    • 後から追加するフラグのために短い名前空間を温存できる
  • 複数のファイルに同じ単純な操作を適用する場合は、複数の引数が適切である
    • rm file1.txt file2.txt file3.txt のような形式はグロビングと相性がよい
  • 異なる意味の引数が2つ以上あるなら、設計が誤っている可能性が高い
    • 例外として cp <source> <destination> のように一般的で中核的な動作は、短いことに記憶する価値がある
  • 標準的なフラグ名があるなら、それに従うべき
    • -a, --all
    • -d, --debug
    • -f, --force
    • --json
    • -h, --help
    • -n, --dry-run
    • --no-input
    • -o, --output
    • -p, --port
    • -q, --quiet
    • -u, --user
    • --version
  • デフォルト値は、ほとんどのユーザーにとって正しい動作であるべき
    • ユーザーが適切なフラグを見つけ、毎回覚えて使うと仮定すると、多くのユーザー体験は悪化する
  • 入力がなければプロンプトで尋ねてもよいが、プロンプトが必須であってはならない
    • 常にフラグまたは引数で入力を渡す方法を提供すべき
    • stdin が対話型ターミナルでなければ、プロンプトをスキップし、必要なフラグや引数を要求すべき
  • 危険な操作の前には確認すべき
    • 対話型実行では yyes を入力させてもよい
    • 非対話型実行では -f--force を要求できる
    • 重大な操作では、削除対象の名前を直接入力させるか、--confirm="name-of-thing" のようなフラグを提供できる
  • ファイル入出力を受け取るなら、-stdin または stdout をサポートすべき
    • 一時ファイルなしで他のコマンド出力と接続できる
  • 可能な場合は、引数、フラグ、サブコマンドの順序を独立して処理すべき
    • ユーザーが前のコマンドの末尾にオプションを追加して再実行することがよくあるためである
  • シークレット値をフラグで直接読み取るべきではない
    • --password の値は ps 出力やシェル履歴に露出する可能性がある
    • --password-file のようなファイル入力や stdin を検討すべき

インタラクション

  • プロンプトや対話型要素は、stdin が TTY のときだけ使うべき
    • スクリプトやパイプ入力中はプロンプトが機能しないため、どのフラグを渡すべきかをエラーで知らせるべき
  • --no-input が渡されたら、プロンプトや対話型の動作を行うべきでない
    • 入力が必要なら失敗し、フラグで渡す方法を案内すべき
  • パスワードを入力させるときは、ユーザーがタイプする値を出力すべきでない
    • ターミナルの echo をオフにする方法で処理する
  • ユーザーが抜け出せるべき
    • ネットワーク I/O などで停止していても Ctrl-C が機能すべき
    • SSH、tmux、telnet のように Ctrl-C で終了できないラッパーなら、脱出方法を明確に知らせるべき

サブコマンド

  • ツールが十分に複雑なら、サブコマンド群で複雑さを減らせる
    • 複数の関連ツールを1つのコマンドにまとめると、使いやすく見つけやすくなる場合がある
    • グローバルフラグ、ヘルプ、設定、保存メカニズムを共有しやすい
  • サブコマンド間には一貫性が必要
    • 同じ意味には同じフラグ名を使うべき
    • 出力フォーマットも似ているべき
  • 複数階層のサブコマンドには、一貫した命名体系を使うべき
    • docker container create のように、名詞と動詞の2段階で構成するパターンが一般的である
    • noun verbverb noun はどちらも可能だが、noun verb のほうが一般的に見える
  • あいまい、または似た名前のコマンドは避けるべき
    • updateupgrade が併存していると混乱を招く可能性がある

堅牢性

  • ユーザーが入力するすべてのデータは検証する必要がある
    • 不正なデータが入る可能性があるため、早めにチェックし、理解しやすいエラーで中断する必要がある
  • 速さよりも応答性のほうが重要
    • 100ms以内にユーザーへ何かを出力するのが望ましい
    • ネットワークリクエストの前にもメッセージを出力し、止まったように見えないようにする必要がある
  • 時間のかかる作業では進捗状況を表示する必要がある
    • しばらく出力がないと、プログラムが壊れたように見える
    • 進捗表示が長い間同じ場所で止まっている場合は、残り時間の推定やアニメーションで作業中であることを示せる
  • 並列処理は可能なら行うべきだが、慎重であるべき
    • 並列作業の進捗率をシェルで表示するのは難しく、出力が混ざると混乱を招く
    • docker pullの複数の進捗バーは、何が起きているかを示す例である
    • 正常動作時にはログを進捗バーの後ろに隠すとしても、エラーが発生したらデバッグできるようログを出力する必要がある
  • ネットワーク処理にはタイムアウトを設ける必要がある
    • タイムアウトは設定可能で、妥当なデフォルト値が必要
  • 失敗後に復旧できる必要がある
    • 一時的な失敗後、<up><enter>でもう一度実行したときに、中断した地点から再開できる必要がある
  • 可能ならcrash-only方式が望ましい
    • 失敗や中断時に即座に終了でき、後片付けは次回の実行に先送りできる
  • ユーザーはツールを想定外の方法で使う可能性がある
    • スクリプトでラップしたり、不安定なインターネット環境で使ったり、複数インスタンスを同時に実行したり、テストしていない環境で実行したりする可能性がある

将来互換性

  • サブコマンド、引数、フラグ、設定ファイル、環境変数はすべてインターフェースであり、長く動作するよう維持する必要がある
  • 変更は可能な限り追加的であるべき
    • 既存フラグの動作を壊す代わりに、新しいフラグを追加できる
  • 非追加的な変更が必要な場合は、事前に警告する必要がある
    • 推奨されなくなったフラグを使ったときに将来の変更を知らせ、今から使える将来互換の方法も伝える必要がある
  • 人間向けの出力は変わっても概ね問題ない
    • ユーザーには、スクリプトで安定した出力を得るために--plain--jsonを使うよう促すべき
  • catch-allサブコマンドは避けるべき
    • 既知のサブコマンドでなければ自動的にrunとして扱う方式は、後で新しいサブコマンドを追加する際に既存の使い方を壊す可能性がある
  • サブコマンドの任意の省略形を許可すべきではない
    • installiinsとして許可すると、後でiで始まる別のコマンドを追加しにくくなる
    • エイリアスは明示的かつ安定したものに保つ必要がある
  • 20年後にもコマンドが同じ方法で実行されるかを考える必要がある
    • 外部インターネット依存が変わったり消えたりしてコマンドが止まる時限爆弾を作るべきではない

シグナルと制御文字

  • ユーザーがCtrl-C、つまりINTシグナルを送ったら、できるだけ早く終了する必要がある
    • 後片付け作業を始める前に、ただちに何かを伝える必要がある
    • 後片付けコードにはタイムアウトを設ける必要がある
  • 時間のかかる後片付け中にCtrl-Cが再度入力されたら、後片付けをスキップできる必要がある
    • Docker Composeは、終了中にCtrl-Cをもう一度押すとコンテナを即座に強制停止できると案内する
  • プログラムは、以前の後片付け作業が実行されていない状態で起動される可能性があると想定する必要がある

設定と環境変数

  • 設定方法は具体性安定性複雑性に応じて変えるべき
    • 実行ごとに頻繁に変わる設定にはフラグが適している
    • ユーザー・プロジェクト・マシンによって異なる比較的安定した設定には、フラグと環境変数が適している
    • プロジェクト内の全ユーザーに対して安定した設定には、バージョン管理されるコマンド専用の設定ファイルが適している
  • XDG Base Directory Specificationに従うのが望ましい
    • ~/.configのような汎用設定場所をサポートし、ホームディレクトリ内のdotfile増加を抑える目的がある
  • 自分のプログラムの設定ではないファイルを自動修正する場合は、ユーザーの同意を得て、正確に何をするのかを知らせる必要がある
    • 既存のシステム設定ファイルに追記するより、新しい設定ファイルを作るほうが望ましい
  • 設定の優先順位は高いものから低いものの順に適用する必要がある
    • フラグ
    • 実行中のシェルの環境変数
    • プロジェクトレベルの設定
    • ユーザーレベルの設定
    • システム全体の設定
  • 環境変数は、コマンドが実行されるコンテキストによって変わる動作に適している
  • 環境変数名は最大限の移植性のため、大文字、数字、アンダースコアのみを使い、数字で始めるべきではない
  • 値は可能なら1行にすべき
    • 複数行の値はenvコマンドと一緒に使う際にユーザビリティ上の問題が生じる
  • 広く使われている名前を不用意に占有すべきではない
    • POSIX標準環境変数の一覧を参考にできる
  • 可能な場合は汎用環境変数を確認する必要がある
    • NO_COLOR, FORCE_COLOR
    • DEBUG
    • EDITOR
    • HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY
    • SHELL
    • TERM, TERMINFO, TERMCAP
    • TMPDIR
    • HOME
    • PAGER
    • LINES, COLUMNS
  • 適切な場合は.envから環境変数を読み取れる
    • 特定のディレクトリで作業している間、ほとんど変わらない変数に有用
  • .envは正式な設定ファイルの代替ではない
    • ソース管理に保存されないことが多い
    • 文字列型しかない
    • 整理が悪くなりやすい
    • エンコーディング問題が起きやすい
    • 機密性の高い資格情報や鍵素材が入りやすい
  • 秘密値は環境変数から読み取るべきではない
    • 環境変数はプロセス、ログ、docker inspectsystemctl showなどを通じて露出する可能性がある
    • 秘密は資格情報ファイル、パイプ、AF_UNIXソケット、シークレット管理サービス、その他のIPC方式で受け取るべき

名前、配布、分析収集

  • CLIプログラム名はユーザーが頻繁に入力するため、シンプルで覚えやすいものにすべき
    • あまりに一般的だと他のコマンドと衝突したり、ユーザーを混乱させたりする可能性がある
  • 名前は小文字と、必要な場合のハイフンだけを使うのが望ましい
    • curlは良い名前で、DownloadURLはそうでない例である
  • 名前は短いほうがよいが、短すぎる名前はcdlspsのような非常によく使われるユーティリティにふさわしい
  • 入力しやすい名前であるべき
    • Docker Composeの初期名がplumだったが、片手で打ちにくいためfigに変わった事例がある
  • 可能なら単一バイナリとして配布すべき
    • 単一バイナリが難しければ、プラットフォーム標準のパッケージインストーラを使い、削除しやすい方法で配布する必要がある
    • 言語別ツール、たとえばコードリンターは、ユーザーがその言語のインタープリタを持っていると想定してもよい
  • 削除しやすい必要がある
    • インストール直後に削除したくなる場合は珍しくないため、削除手順をインストール手順の下に置くのが望ましい
  • 利用状況メトリクスはツール改善に役立つ可能性があるが、CLIユーザーは自分の環境を自分で制御できると期待している
  • 同意なしに利用状況やクラッシュデータを送信すべきではない
    • 何を収集するのか、なぜ収集するのか、どの程度匿名化されるのか、どのように匿名化するのか、どれくらいの期間保存するのかを明示する必要がある
    • 理想的にはオプトインがよく、デフォルトで収集するオプトアウト方式を選ぶなら、Webサイトや初回実行時に明確に知らせ、簡単に無効化できる必要がある
  • 分析収集の代替案も検討できる
    • Webドキュメントを計測する
    • ダウンロードを計測する
    • ユーザーと直接話し、ドキュメントやリポジトリでフィードバックや機能リクエストを促す

1件のコメント

 
GN⁺ 2024-02-07
Hacker News のコメント
  • 「今ではほとんどの人がコマンドラインが何かも知らない」というのは正しいが、TFA が基準にしている1980年代でも同じだった
    違いは、今がコマンドラインを知って使える人の数が歴史上最も多いという点で、少なくとも一桁、もしかすると二桁の規模で増えているのだから、CLI の黄金時代と言ってもよい

    • モノリスを分割するために、アプリの一部にコマンドラインツールを付けている
      グローバル状態と依存関係は推論を妨げ、結局デバッグや性能最適化も難しくする
      500行、1,000行、5,000行、10,000行、ときには5万行のコードを切り出して別に実行できるようにすると、多くのことがずっと明確になる
      うまくやれば Functional Core, Imperative Shell の構造も促せる。Unix 哲学に沿ったコマンドラインは副作用のない動作が多く、副作用のある動作は少ないべきだからだ
      悪い判断が下される直前のシステム状態を生成して出力する小さなコマンドを作ることができ、本番環境でも実質的に安全に実行できる
      そうすれば、バスファクターに含まれるべき人にもこうしたツールを渡して参加してもらいやすくなる
    • 「人」を「コンピュータユーザー」に置き換えれば、筆者の要旨は正しい
      アマゾン奥地の文明から離れた村の人がターミナルをどう考えるかは関係ない
    • 絶対数と比率を区別する必要がある
      数量が大きくなった対象の質的変化を判断するには比率を見るべきで、絶対数だけを見ると、真ではあるが意味のない文になる
      例えば今日のジャズ聴取者の絶対数は、このジャンルの文化的全盛期より多いかもしれないが、それはジャズの人気が高まったからではなく、音楽を聴く人自体が増えたからだ
      だからといって、米国でジャズが全盛期より重要で影響力があるとは言いにくい
    • 1980年代のコンピュータユーザーの中で、比率で見てもそうだったのかが核心だ
  • 実際に変更せず、どんな処理が起きるかを事前に示す --dry-run オプションも検討してほしい
    ツールを学ぶときや、元に戻しにくい処理を実行する前に、複雑なオプションやファイル glob を正しく使えているか確認するのに本当に役立つ

    • コマンドを取り消せず副作用が大きいなら、デフォルトを --dry-run にして、実行には --execute フラグを要求するほうがよい
    • PowerShell の WhatIf は一番好きな機能の一つだ
      これなしでどうしていたか分からないし、何度も完全に壊しかけた状況から救ってくれた
    • 逆に、デフォルトの動作では実際の変更を行わないようにし、本当に実行するには --commit を渡す形のほうが好みだ
      取り消せない、または取り消しにくい処理をするスクリプトでは、この方式のほうが安全に感じる
    • dry run フラグがあるコマンドラインツールの例が知りたい
      作っているツールでどう設計すべきか混乱している
      データを取得するために API を呼び出す場合、それをどう dry run にできるのか分からない
      架空の例と架空の出力を出すべきなのか?
    • dry-run は、どんなコマンドにもパイプでつなげられる関数であるべきだと思う: do_thing.py | dry-run
      プロンプトで「作業手順を段階的に説明して」と頼むようなものだと考えればいい
  • 標準出力が対話型ターミナルでない場合はアニメーションを表示しない、という程度ではなく、stdout にはアニメーションを絶対に表示してはいけない
    TFA は全体的には良かったが、stderr と stdout の違いを説明する部分を探していて、そうしていないのを見て残念だった
    stderr はエラーだけでなく、ログや情報的な出力すべてが行くべき場所であり、ターミナルならアニメーションを入れることもできる領域だ
    stdout は有用な実際の出力であるべきで、ターミナルかどうかに関係なく一貫しているべきだ
    例えば echo foo | mysed 's/oo/aa/' | cat では、mysed の stdout は faa で、stderr にはバージョン情報や見つかった内容のようなログが行くべきだ
    実際の出力だけを得るために grep でツールと格闘したくないし、| cat を外すと動作が変わるような、デバッグしにくい形にもしたくない

    • 「stdout は有用な出力」という規則を少し調整すると、stdout はユーザーが要求したものであるべきだ
      --help を要求したなら stdout に行くべきだし、ログを要求したならログも stdout に行くべきだ
      要求していない情報なら stderr が正しい
    • 良い規則だが、名前が惜しい
      時間を戻せるなら stdin、stdout、stdext のような名前にしていたら、人々がこの慣習に従う可能性があったと思う
      しかし名前が stderr なので、開発者は合理的に「エラー報告用」だと考え、エラーでなければ stdout に入れてしまう
      それでもプログラム構造としてはこの方式のほうが良く、最初はより混乱するかもしれないが、出力でより有用なことができるので利点がある
    • だとすると、アニメーションはどこに表示すべきなのか気になる
      色も情報とは言いにくいと思う
  • 「明確になるところでは記号や絵文字を使え」という助言は、どうか避けてほしい
    例として挙げられている yubikey-agent は、GitHub README と遊び心のあるユーザーインターフェースで嫌いな点をそのまま示している
    技術的には、記号や絵文字はターミナルごとに異なってレンダリングされ、メッセージを分かりにくくすることがある
    美的にも、遊び心や軽快さに対する許容度は人によって大きく違うので、ごく控えめに、本当に何をしているか分かっている場合にだけ使うべきだ

    • 私はそういう出力が好きだ
      色付きのターミナル出力も好きだし、絵文字も色があるので状況の全体像を素早く把握する助けになる
      シンタックスハイライトも好きで、ないとコードがずっと読みにくく感じる
      誰もがそうではないし、自分の好みがデフォルトに設定されることを期待しないのと同じように、反対の好みもデフォルトとして押し付けるべきではない
    • 選択機能なら良い
      好きな人も嫌いな人もいるので、デフォルトではオフにしておき、ユーザーが選べるようにすればいい
    • 指針としては、出力に使う絵文字の語彙を最大2つに制限すると良いと思う
      例えば成功が1つ、失敗が1つくらいで十分だ
      そして絵文字が伝える情報は、必ずテキストでも重複して伝えるべきだ
    • 強く同感だ
      初めて CLI Guidelines を見たときも、そのセクションで読むのをやめた
      今回は残りも読んでみたが、全体としてはかなり良かった
  • 一部のCLIが aws のように非常に大きく、入れ子が必要になるのは理解できるが、入れ子のCLIをたどっていくのは本当にイライラする。
    たいていのアプリは help にすべてのオプションを吐き出して、こちらが less で必要なものを探せるようにしてくれるほうがよい。

    • TUIアプリを多く作っているが、すべてのアプリに、あまり技術に詳しくない人、たとえばQAが使える良いTUIフロントエンドを付けている。
      このTUIは、使いたければ使う純粋なUI/UXで、選択した設定を別の生成ファイルや関数に渡す。
      そのファイルや関数は常にターミナルから直接呼び出せて、TUIで使うすべてのオプションとヘルプも提供する。
      そのためスクリプトからも使え、熟練度の異なるユーザーにも役立つ。
    • サブコマンドの数と、それらのコマンドがどれだけ明確かによる。
      必要なサブコマンドが分かっているなら、オプションを必要なものだけに絞ってくれるのはかなり有用だが、分からないと苦痛になり得る。
      大量のオプション一覧を grep で探すのも大変なので、バランスが必要だ。
    • それは man ページの役割ではないのか?
    • サブコマンドの --help を上位コマンドに展開して入れる方式が役立つこともある。
      たとえば git stash --help が、各項目を含む git stash のヘルプをそのまま表示するような形だ。
  • 「伝統的にUNIXコマンドは主に他のプログラムが使うことを前提に書かれた」という説明は正確ではない。
    もともとはログインシェル内での対話的な利用が主に想定されていた。
    stdout に出力を作るプログラム(ls, cat, find, tty, who, date)と、静かなテキストフィルタ(tr, grep, cut, uniq, sort, wc)があり、その時代には1行コマンドで基本的なコンピューティング作業ができた。
    複雑なプログラムはCで書かれ、sed や AWK のようなドメイン固有言語が登場した後、文字列処理の多い一部のプログラムがシェルへ移っていった。
    シェルはまともなプログラミング環境ではなく、そのような用途を意図されたこともない。

    • 1万行のCプログラムがシェル10行で済む場合もあれば、1000行のシェルプログラムがCなら100行で済んだ場合もある。
      最近はどちらも PythonやLua でやるほうがよいことが多いが、シェルとCが最も広くインストールされている。
    • まともかどうかはともかく、シェルはプログラミング言語であり、初期のUnixではその点がずっと際立っていた。
      sh, bash, ksh, csh に分かれたことも良い例だ。
      標準POSIXツール群を、複数のシェル言語における標準ライブラリと見るのはかなり妥当だと思う。
  • 現在のUnixコマンドラインは、一方では「ものすごく有用」であり、もう一方では設計上壊れている
    次をCやRustで書こうとしたらどれだけ時間がかかるかを考えれば、有用性は明らかだ。
    curl -sS https://go.dev/doc/devel/release | html2text | grep -o -P '\bgo\d+\.\d+\.\d+\b' | sort -V | uniq | tail -1
    しかし、なぜ設計上壊れているのかは https://news.ycombinator.com/item?id=29747034 を見ればよい。
    問題は、コマンドラインインターフェースが人間に読みやすく、同時に機械にも読みやすくなければならない点であり、これを解決する標準的な方法がないことだ。

    • その例自体が、なぜ設計上壊れているのかをよく示している。
      人間と機械が同時に読めなければならないという問題には、本来なら標準的な解法があり得たかもしれない。
      AppleはデスクトップUIの視覚的抽象化の意味を具体化しようとして Human Interface Guidelines を作った。
      しかしコマンドラインは、Appleのデザイナーのように考える人たちではなく、「怠惰、短気、傲慢は美徳なのだから、自分が欲しいものを最小限のコードでどう表現するか」と考える人たちが作った。
      当時としては間違った選択でもなく、1バイト1バイトが重要だったが、その設計判断が今では動かしようのないツールチェーンに埋め込まれてしまった。
      今より良いものを得るには、POSIXツールチェーンを事実上諦める必要がありそうで、現在の基盤の上に発見しやすく概念的に一貫したUXを積み上げるのは想像しにくい。
    • コンピュータは結局われわれに奉仕するためにあるのだから、最終的な解法は機械を人間と同じくらい上手に読めるようにすることであるべきだ。
    • 実際には「同時に」というより、たいていは人間と機械が同じコマンドを別々の時点で使う。
      同じ時点でも、異なる出力を可能にする方法はたくさんある。
      ただし、全員が従う標準が必要で、まさにそこで複雑になる。
    • その例は、設計上壊れていることを証明していないだけでなく、比較的簡単な解決策まで示しているように見える。
      ls の実装の中で、ファイル名を改行ではなくNUL文字で終端できるものがほとんどないという問題があるなら、解決策はあまりに明白に思えるので、何か見落としている気がする。
      シェルインターフェースが何らかの理由で拒否しているのだろうか?
    • 記事で扱っている方法は、--json のような機械可読な出力モードだ。
  • 「シークレット値を環境変数から読み取るな」としつつ、認証情報ファイル、パイプ、AF_UNIX ソケット、シークレット管理サービス、その他のプロセス間通信を提案しているが、この中で何が一番手軽で移植性が高いのか気になる
    シークレット管理サービスは仕事でだけ使うものなのか、個人プロジェクトでも使うのかも気になる

    • CLI Guidelines の著者の一人です
      コマンドラインでシークレット値を扱う内容をもう少し深く書いた記事は https://smallstep.com/blog/command-line-secrets/ にあります
      認証情報ファイルはシンプルで移植性の高い選択肢です
      ファイルにはすでに権限モデルがあり、外部サービスや独自 API に依存しません
      プログラムが認証情報ファイルを受け取るなら、systemd credentials とも互換性があります
      systemd credentials は暗号化されていない認証情報ファイルより安全で、暗号化され、TPM にひも付けることもできますが、認証情報を使うソフトウェア自体が TPM をサポートする必要はありません
    • プログラムがシークレット値を取得するコマンドを指定できるようにしてくれるとよい
      たとえば mbsync(https://isync.sourceforge.io/mbsync.html) には IMAP 認証パスワードを提供する方法がおおよそ 3 つあります
      パスワードを設定しなければ実行時にプロンプトが出ますし、設定ファイルに平文のパスワードを入れることもできますが、共有するには不便です
      さらにパスワードを取得するコマンドを設定できるので、pass(1)(https://www.passwordstore.org/) のようなパスワードマネージャや対話型のグラフィカルなプロンプトに処理を委ねられます
    • シークレット管理サービスが最も便利でしょう
      ドキュメントがよく整備されていて、自分で追加で作り込む必要なく簡単に設定できます
      Doppler(https://Doppler.com) や AWS Secrets Manager(https://aws.amazon.com/secrets-manager/) のようなシークレットマネージャには、シークレット値を安全な場所で保護し、その露出を最小化できる利点があります
      内部開発者に対してさえ露出を減らせるため、容易に避けられたはずのデータ漏えいを防ぐ助けになります
      こうした漏えいは会社全体を危険にさらす可能性があり、ますます一般的になっています
  • 関連記事: Command Line Interface Guidelines - https://news.ycombinator.com/item?id=38053692 - 2023年10月、コメント1件
    Command Line Interface Guidelines - https://news.ycombinator.com/item?id=31651161 - 2022年6月、コメント1件
    Command Line Interface Guidelines - https://news.ycombinator.com/item?id=25492119 - 2020年12月、コメント5件

  • クリップボードから貼り付けた文字列の末尾に改行文字があると、一部のターミナルが自動的にコマンドを実行してしまうのが本当にいら立つ
    個人的には、コマンドラインインターフェースはそうあるべきではないと思う

    • Bash では bind 'set enable-bracketed-paste on' を使えばよい
    • macOS の iTerm は、改行を含む文字列を貼り付けようとすると確認を求める
      面倒なら無効にもできる
    • Fish shell はデフォルトで期待どおり挿入するだけで、実行前にコマンドを編集できるようにしてくれる
      必要なら複数行も可能で、Enter を押して初めて実行される
      Fish shell はデフォルトが妥当なことが多く、プロンプト以外では特にカスタマイズしたい部分をまだ見つけていない
    • クリップボードマネージャを試してみる価値はある
      自分が試したものの多くには、クリップボードの内容から改行を削除するオプションがあった
    • 改行が最大 1 つだけだと分かっているなら、先に # を入力してから貼り付ける
      改行が解釈されても行全体がコメントになるので安全で、実際に実行する前に行頭の # だけ消せばよい