-
Notifications
You must be signed in to change notification settings - Fork 0
MS_CLISystemCommandLine
- 戻る(自作CUI(CLI)の話)
- CLI開発(Sharprompt)
- CLI開発(System.CommandLine)
自作 CUI(CLI)を開発する際に便利なのでは?と。
https://github.com/dotnet/command-line-api/blob/main/docs/History.md
補足(
System.CommandLineの立ち位置と、注意すべき現況): 本ライブラリは
**Microsoft 公式(dotnet 組織)**であり、
dotnetCLI 自身の設計思想が反映されている。【何を提供するか】 ・コマンドライン引数の【解析】 ・ヘルプの【自動生成】 ・シェルの【タブ補完】の生成 ・エラー メッセージの整形 → 「CLI として当然備えるべきもの」を一通り面倒みる ★移行メモ(バージョンに強く依存する点に注意): 本ライブラリは
長期にわたりプレビュー(beta)のまま提供され、
API が何度も破壊的に変更された。【経緯】 2019~ 2.0.0-beta1 系。長期間 beta のまま 2023頃 2.0.0-beta4 で大きく整理 その後 さらに API を簡素化(Handler の書き方が変わった)★ 【実務での対応】 ・【バージョンを固定して使う】(PackageReference で明示) ・Web 上のサンプルが【どの beta 版のものか】を必ず確認する → 本ページのサンプルも当時のもの ・更新する際は【必ず動作確認する】以降の各節では、原文の内容(概念)は現在も通用するため
そのまま残し、現在の書き方を補足する形をとる。
-
コマンドライン・パーサー
-
コマンド
- ルート・コマンド
- サブ・コマンド
-
オプション
-
スタイル
・POSIX(-XXX)
・Windows(/XXX) -
エイリアス
・POSIX(-v と --verbose)
・Windows(エイリアス無し)
-
-
引数
-
デリミタ
・スペース
・「:」や「=」 -
arity
引数の最小値と最大値を設定できる。 -
バンドル
・1文字のオプションはバンドル可能。
・バンドルした場合、バンドル内の最後のオプションに引数が適用される。
-
-
バインディング
- ハンドラ設定
- 引数のバリデーション
- 引数のバインディング
- モデル・バインディング
https://github.com/dotnet/command-line-api/blob/main/docs/model-binding.md
-
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はこれを標準で解釈する。
-
その他
-
ヘルプ
-
ヘルプ
> myapp -h > myapp /h > myapp --help > myapp -? > myapp /? -
解析ディレクティブ
構文解析結果を表示する。> myapp [parse] > myapp [parse] ...(コマンド)... -
バージョン
> myapp --version 1.0.0
-
-
Debug ディレクティブ
> myapp [debug] ...(コマンド)... ココの後、デバッガをアタッチしてデバッグ -
パイプライン
- ハンドラへのルーティングの前の
パイプラインに呼び出しを追加できる。 - ディレクティブは、この機能を使用している。
- ハンドラへのルーティングの前の
-
-
参考
補足(ディレクティブが秀逸):
[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 のミドルウェアと同じ発想である
(処理の前後に層を挟む)。
ログ出力、実行時間の計測、例外の整形などを
全コマンド共通で挟める。
強力に型付けされた 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)の話)が
現役で、サブコマンドにも対応している。
-
構造化された端末出力のレンダリング
-
ANSI のサポート
-
参考
移行メモ(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)の話 の「色と装飾は環境を見て切る」)。
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 のプロジェクト・テンプレートの変遷)。
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のような形)。
-
https://github.com/dotnet/command-line-api/blob/main/docs/Your-first-app-with-System-CommandLine.md
-
OpenTouryoProject/OpenTouryo
補足(現在の書き方の例): バージョンによって書き方が変わるため、
概念が分かる形で示す(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 を定義に持たせる → ヘルプにも自動的に反映される
...
...
-
dotnet/command-line-api: Command line parsing, invocation, and rendering of terminal output.
https://github.com/dotnet/command-line-api -
DragonFruit and System.CommandLine is a new way to think about .NET Console apps - Scott Hanselman's Blog
https://www.hanselman.com/blog/dragonfruit-and-systemcommandline-is-a-new-way-to-think-about-net-console-apps -
Getting Started with System.CommandLine – .NET Development Addict
https://dotnetdevaddict.co.za/2020/09/25/getting-started-with-system-commandline/ -
Parsing the command line for your application with System.CommandLine – Bruno Sonnino
https://blogs.msmvps.com/bsonnino/2020/04/12/parsing-the-command-line-for-your-application-with-system-commandline/ -
System.CommandLine 入門 | Yucchiy's Note
https://blog.yucchiy.com/2021/03/intro-system-commandline/ -
C#でコマンドライン引数を扱う方法 - 備忘録
https://kagasu.hatenablog.com/entry/2018/12/17/165431 -
.NET での CLI 処理ライブラリについて - 鷲ノ巣
https://tech.blog.aerie.jp/entry/2018/06/22/124630 -
.NET - System.CommandLine を使用してコマンド ラインを解析する | Microsoft Docs
https://learn.microsoft.com/ja-jp/archive/msdn-magazine/2019/march/net-parse-the-command-line-with-system-commandline
- System.CommandLine の概要
https://learn.microsoft.com/ja-jp/dotnet/standard/commandline/ - コマンドライン アプリのチュートリアル
https://learn.microsoft.com/ja-jp/dotnet/standard/commandline/get-started-tutorial - .NET ツールの概要(グローバル ツール)
https://learn.microsoft.com/ja-jp/dotnet/core/tools/global-tools
Tags: 移行, シェル, インフラストラクチャ, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。