Skip to content

MS_CLISystemCommandLine

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

CLI開発(System.CommandLine)

概要

自作 CUI(CLI)を開発する際に便利なのでは?と。

https://github.com/dotnet/command-line-api/blob/main/docs/History.md

補足(System.CommandLine の立ち位置と、注意すべき現況): 本ライブラリは
**Microsoft 公式(dotnet 組織)**であり、
dotnet CLI 自身の設計思想が反映されている

【何を提供するか】
   ・コマンドライン引数の【解析】
   ・ヘルプの【自動生成】
   ・シェルの【タブ補完】の生成
   ・エラー メッセージの整形
      → 「CLI として当然備えるべきもの」を一通り面倒みる ★

移行メモ(バージョンに強く依存する点に注意): 本ライブラリは
長期にわたりプレビュー(beta)のまま提供され、
API が何度も破壊的に変更された

【経緯】
   2019~   2.0.0-beta1 系。長期間 beta のまま
   2023頃   2.0.0-beta4 で大きく整理
   その後   さらに API を簡素化(Handler の書き方が変わった)★

【実務での対応】
   ・【バージョンを固定して使う】(PackageReference で明示)
   ・Web 上のサンプルが【どの beta 版のものか】を必ず確認する
      → 本ページのサンプルも当時のもの
   ・更新する際は【必ず動作確認する】

以降の各節では、原文の内容(概念)は現在も通用するため
そのまま残し、現在の書き方を補足する形をとる。

詳細

機能

System.CommandLine

  • コマンドライン・パーサー

    • コマンド

      • ルート・コマンド
      • サブ・コマンド
    • オプション

      • スタイル
        ・POSIX(-XXX)
        ・Windows(/XXX)

      • エイリアス
        ・POSIX(-v と --verbose)
        ・Windows(エイリアス無し)

    • 引数

      • デリミタ
        ・スペース
        ・「:」や「=」

      • arity
        引数の最小値と最大値を設定できる。

      • バンドル
        ・1文字のオプションはバンドル可能。
        ・バンドルした場合、バンドル内の最後のオプションに引数が適用される。

    • バインディング

    • Ctrl-C
      https://github.com/dotnet/command-line-api/blob/main/docs/Process-termination-handling.md

    • レスポンス・ファイル
      レスポンス・ファイルを使用してコマンドを指定できる。

補足(用語の具体化): 一覧が簡潔なので、
実際のコマンドラインでの意味を示しておく。

【コマンド/サブコマンド】
   dotnet build --configuration Release
   └─┬─┘ └─┬─┘ └────────┬────────┘
   ルート  サブ         オプション

【arity(引数の個数)】
   ExactlyOne  … --name foo            (必ず 1 つ)
   ZeroOrOne   … --verbose             (値なしでもよい)
   ZeroOrMore  … --file a.txt b.txt    (0 個以上)
   OneOrMore   … 少なくとも 1 つ
     → 「--file を 3 回まで」といった制約も表現できる ★

【バンドル】
   -a -b -c   →  -abc      (1 文字オプションをまとめる)
   -abc value →  c に value が適用される(最後のオプションに付く)

【デリミタ】
   --name foo   /  --name:foo  /  --name=foo   … すべて同じ

レスポンス ファイルは知られていないが便利である。

【問題】 コマンドライン長には OS の上限がある(Windows は約 32,767 文字)
         → 大量のファイルを引数で渡すと超える ★

【解】  引数をファイルに書き、@ で参照する
         > myapp @args.rsp

         args.rsp:
           --input a.txt
           --input b.txt
           --verbose

csc.exe(C# コンパイラ)や MSBuild が古くから使う仕組みで、
System.CommandLine はこれを標準で解釈する。

補足(ディレクティブが秀逸): [parse][debug]
この種のライブラリでは珍しい機能で、実務で非常に役に立つ。

【[parse] ── 引数がどう解釈されたかを見る】★
   > myapp [parse] create --name foo -c 3
   [ myapp [ create <foo> [ --count <3> ] ] ]

   → 「なぜこのオプションが効かないのか」を
     【実行せずに】確認できる
   → 引数の設計を検証する際にも使える

【[debug] ── デバッガをアタッチする】★
   > myapp [debug] create --name foo
   Attach your debugger to process 12345

   → 【CLI をデバッグする難しさ】を解消する
     (通常、CLI は起動して即実行されるため、
       デバッガを付ける隙がない)
   → 本番環境で再現する不具合の調査に有用

ヘルプの自動生成も重要な価値である。

【手で書くと必ず陳腐化する】
   ・オプションを追加したのにヘルプを直し忘れる
   ・既定値を変えたのにヘルプが古い

【自動生成なら】
   ・定義が唯一の情報源(single source of truth)★
   ・ずれようがない

パイプラインOWIN /
ASP.NET Core のミドルウェアと同じ発想である
(処理の前後に層を挟む)。
ログ出力、実行時間の計測、例外の整形などを
全コマンド共通で挟める。

System.CommandLine.DragonFruit

強力に型付けされた Main メソッドを使って、
慣習的にコマンドラインアプリを構築する。

補足(DragonFruit の発想と現況): **「規約による設定」**を
極端に推し進めたもので、発想が面白いので補足する。

// DragonFruit:Main のシグネチャが【そのままコマンドライン仕様になる】★
/// <summary>ファイルを処理します。</summary>
/// <param name="input">入力ファイル</param>
/// <param name="verbose">詳細出力</param>
/// <param name="count">繰り返し回数</param>
static void Main(FileInfo input, bool verbose = false, int count = 1)
{
    // --input file.txt --verbose --count 3 が自動で解析されてくる
    // XML ドキュメント コメントが【ヘルプ文になる】
}
【利点】
   ・記述量が【ほぼゼロ】
   ・型(FileInfo、int、enum)から解析規則が決まる
   ・XML コメントがヘルプになる= ドキュメントと実装がずれない

【限界】
   ・サブコマンドが作れない(Main は 1 つしかない)
   ・細かい制御ができない(エイリアス、arity、検証)
      → 【単機能のツール向け】★

移行メモ: DragonFruit は現在ほぼ更新されていない
同種の「規約ベース」の選択肢としては、
ConsoleAppFramework自作CUI(CLI)の話)が
現役で、サブコマンドにも対応している

System.CommandLine.Rendering

移行メモ(Rendering は開発が止まった): System.CommandLine.Rendering
実験的なパッケージのまま、事実上開発が停止している。

【現在、端末への描画をしたいなら】
   ・【Spectre.Console】★ 表・ツリー・進捗・色・マークアップ
   ・Terminal.Gui       … 全画面 TUI
   ・自前で ANSI エスケープ … 単純な色付けだけなら十分
// 単純な色付けなら標準 API で足りる
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("エラー");
Console.ResetColor();

// ただし、リダイレクト時は色を出さない配慮が要る ★
if (!Console.IsOutputRedirected && Environment.GetEnvironmentVariable("NO_COLOR") is null)
{ /* 色を使う */ }

NO_COLOR 環境変数は近年広く尊重されている慣習である
自作CUI(CLI)の話 の「色と装飾は環境を見て切る」)。

System.CommandLine.Hosting

Microsoft.Extensions.Hosting で System.CommandLine を使用するためのサポート

補足(Hosting との統合の意味): これは
DI と設定とログを CLI にも持ち込むためのものである。

// ハンドラの引数に、DI 登録したサービスを注入できる ★
var builder = Host.CreateApplicationBuilder();
builder.Services.AddSingleton<IOrderService, OrderService>();
builder.Services.AddDbContext<AppDbContext>(...);
【これが効く場面】
   ・CLI から【Web アプリと同じサービス層】を呼びたい
      → 例: バッチ処理、データ移行ツール、管理コマンド
   ・appsettings.json / 環境変数から設定を読みたい
      → [.NET Core における DI](MS_DotNetCoreDI)
        [FaaS config](MS_FaaSConfig) と同じ仕組み
   ・ILogger でログを出したい
      → [.NETのログ](MS_DotNetLogging)

 → 【Web / バッチ / CLI で実装を共有できる】★

現在は Microsoft.Extensions.Hosting を直接使い、
Worker Service テンプレートから作る
方が素直な場合も多い
ASP.NET のプロジェクト・テンプレートの変遷)。

dotnet-suggest

System.CommandLine を使って作られたアプリに
シェルの補完機能を提供するコマンドラインツール。

補足(タブ補完は CLI の使い勝手を大きく変える): 地味だが
利用者体験に最も効く機能である。

【仕組み】
  ① シェル(PowerShell / bash / zsh)に補完スクリプトを登録
  ② Tab を押すと、シェルが dotnet-suggest を呼ぶ
  ③ dotnet-suggest が【対象アプリに問い合わせて】候補を返す
      → アプリ自身が候補を知っているので、【常に最新】★
# PowerShell への登録($PROFILE に書く)
dotnet-suggest register --command-path "C:\tools\myapp.exe"
【補完の設計】
   ・オプション名は自動で補完される
   ・【値の候補も指定できる】
       option.AcceptOnlyFromAmong("dev", "stg", "prod");
       → --env <Tab> で 3 つが出る
   ・動的な候補(DB から取得等)も設定できるが、
     【補完のたびに実行される】ので軽い処理に限る ★

現在は dotnet-suggest を使わず、
各シェル向けの補完スクリプトを生成する方式
も一般的である
myapp completion powershell > profile.ps1 のような形)。

サンプル実装

System.CommandLine

補足(現在の書き方の例): バージョンによって書き方が変わるため、
概念が分かる形で示す(2.0 beta4 系以降のスタイル)。

using System.CommandLine;

// オプションと引数を定義する
var fileOption = new Option<FileInfo>("--file", "-f")
{
    Description = "入力ファイル",
    Required = true,
};
var countOption = new Option<int>("--count", "-c")
{
    Description = "繰り返し回数",
    DefaultValueFactory = _ => 1,
};
countOption.Validators.Add(r =>          // ← 検証もここに書く ★
{
    if (r.GetValue(countOption) < 1) r.AddError("count は 1 以上");
});

// サブコマンドを組み立てる
var readCommand = new Command("read", "ファイルを読む")
    { fileOption, countOption };
readCommand.SetAction(parseResult =>
{
    var file  = parseResult.GetValue(fileOption);
    var count = parseResult.GetValue(countOption);
    for (var i = 0; i < count; i++) Console.WriteLine(file!.FullName);
    return 0;                            // ← 終了コードを返す ★
});

var root = new RootCommand("サンプル CLI") { readCommand };
return root.Parse(args).Invoke();
【設計上の要点】
 ・【終了コードを返す】
     → 0 = 成功、非 0 = 失敗
     → CI / スクリプトから使われる以上、唯一の機械可読な結果 ★
 ・検証はハンドラではなく【Validator に書く】
     → 実行前に弾ける。エラー メッセージも整形される
 ・Required / DefaultValue を定義に持たせる
     → ヘルプにも自動的に反映される

System.CommandLine.DragonFruit

https://github.com/dotnet/command-line-api/blob/main/docs/Your-first-app-with-System-CommandLine-DragonFruit.md

System.CommandLine.Rendering

System.CommandLine.Hosting

...

dotnet-suggest

...

参考

Microsoft Learn


Tags: 移行, シェル, インフラストラクチャ, .NET開発

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally