チュートリアル: System.CommandLine の概要
重要
System.CommandLine
は現在プレビュー段階であり、このドキュメントはバージョン 2.0 beta 4 を対象としています。
一部の情報は、リリース前に大きく変更される可能性があるプレリリースされた製品に関するものです。 Microsoft は、ここに記載されている情報について、明示または黙示を問わず、一切保証しません。
このチュートリアルでは、System.CommandLine
ライブラリを使う .NET コマンドライン アプリを作成する方法について説明します。 まず、オプションが 1 つの単純なルート コマンドを作成します。 次に、それをベースにして、各コマンドの複数のサブコマンドと異なるオプションを含む、より複雑なアプリを作成します。
このチュートリアルでは、次の作業を行う方法について説明します。
- コマンド、オプション、引数を作成します。
- オプションの既定値を指定します。
- コマンドにオプションと引数を割り当てます。
- あるコマンド以下のすべてのサブコマンドに再帰的にオプションを割り当てます。
- 複数レベルの入れ子になったサブコマンドを操作します。
- コマンドとオプションの別名を作成します。
string
、string[]
、int
、bool
、FileInfo
、列挙のオプション型を操作します。- オプション値をコマンド ハンドラー コードにバインドします。
- オプションの解析と検証にカスタム コードを使います。
前提条件
- C# 拡張機能を追加した Visual Studio Code などのコード エディター。
- .NET 6 SDK。
または
- .NET デスクトップ開発ワークロードをインストールした Visual Studio 2022。
アプリを作成する
"scl" という名前の .NET 6 コンソール アプリ プロジェクトを作成します。
プロジェクト用に scl というフォルダーを作成し、新しいフォルダーでコマンド プロンプトを開きます。
次のコマンドを実行します。
dotnet new console --framework net6.0
System.CommandLine パッケージのインストール
次のコマンドを実行します。
dotnet add package System.CommandLine --prerelease
ライブラリはまだベータ版なので、
--prerelease
オプションが必要です。
Program.cs の内容を次のコードで置き換えます。
using System.CommandLine; namespace scl; class Program { static async Task<int> Main(string[] args) { var fileOption = new Option<FileInfo?>( name: "--file", description: "The file to read and display on the console."); var rootCommand = new RootCommand("Sample app for System.CommandLine"); rootCommand.AddOption(fileOption); rootCommand.SetHandler((file) => { ReadFile(file!); }, fileOption); return await rootCommand.InvokeAsync(args); } static void ReadFile(FileInfo file) { File.ReadLines(file.FullName).ToList() .ForEach(line => Console.WriteLine(line)); } }
上記のコードでは次の操作が行われます。
型 FileInfo の
--file
というオプションを作成し、ルート コマンドに割り当てます。var fileOption = new Option<FileInfo?>( name: "--file", description: "The file to read and display on the console."); var rootCommand = new RootCommand("Sample app for System.CommandLine"); rootCommand.AddOption(fileOption);
ルート コマンドが呼び出されたときに呼び出すメソッドとして
ReadFile
を指定します。rootCommand.SetHandler((file) => { ReadFile(file!); }, fileOption);
ルート コマンドが呼び出されたときに、指定したファイルの内容を表示します。
static void ReadFile(FileInfo file) { File.ReadLines(file.FullName).ToList() .ForEach(line => Console.WriteLine(line)); }
アプリをテストする
コマンドライン アプリの開発中に、次のいずれかの方法でテストを行うことができます。
dotnet build
コマンドを実行し、scl/bin/Debug/net6.0 フォルダーでコマンド プロンプトを開き、実行可能ファイルを実行します。dotnet build cd bin/Debug/net6.0 scl --file scl.runtimeconfig.json
dotnet run
を使い、次の例のように--
の後にオプション値を含めることで、run
コマンドではなくアプリにオプション値を渡します。dotnet run -- --file scl.runtimeconfig.json
.NET 7.0.100 SDK プレビューでは、コマンド
dotnet run --launch-profile <profilename>
を実行することで、launchSettings.json ファイルのcommandLineArgs
を使用できます。プロジェクトをフォルダーに発行し、そのフォルダーでコマンド プロンプトを開き、実行可能ファイルを実行します。
dotnet publish -o publish cd ./publish scl --file scl.runtimeconfig.json
Visual Studio 2022 で、メニューから [デバッグ]>[デバッグ プロパティ] を選び、[コマンド ライン引数] ボックスにオプションと引数を入力します。 次に例を示します。
その後、Ctrl + F5 キーを押すなどしてアプリを実行します。
このチュートリアルでは、これらのオプションのうち最初のものを使うことを想定しています。
アプリを実行すると、--file
オプションで指定したファイルの内容が表示されます。
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
}
}
}
ヘルプの出力
System.CommandLine
を使うと、自動的にヘルプの出力が用意されます。
scl --help
Description:
Sample app for System.CommandLine
Usage:
scl [options]
Options:
--file <file> The file to read and display on the console.
--version Show version information
-?, -h, --help Show help and usage information
バージョンの出力
System.CommandLine
を使うと、自動的にバージョンの出力が用意されます。
scl --version
1.0.0
サブコマンドとオプションを追加する
このセクションでは、次の作業を行います。
- その他のオプションを作成します。
- サブコマンドを作成します。
- 新しいサブコマンドに新しいオプションを割り当てます。
新しいオプションを使うと、前景と背景のテキストの色、読み上げ速度を構成できます。 これらの機能は、テレプロンプター コンソール アプリのチュートリアルから取得した引用文のコレクションを読み取るために使われます。
このサンプルの GitHub リポジトリから、sampleQuotes.txt ファイルをプロジェクト ディレクトリにコピーします。 ファイルのダウンロード方法については、サンプルとチュートリアルに関するページを参照してください。
プロジェクト ファイルを開き、終了
</Project>
タグの直前に<ItemGroup>
要素を追加します。<ItemGroup> <Content Include="sampleQuotes.txt"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup>
このマークアップを追加すると、アプリのビルド時にテキスト ファイルが bin/debug/net6.0 フォルダーにコピーされます。 そのため、そのフォルダー内の実行可能ファイルを実行するときに、フォルダーのパスを指定しなくても、名前を指定してファイルにアクセスできます。
Program.cs で、
--file
オプションを作成するコードの後に、読み上げ速度とテキストの色を制御するオプションを作成します。var delayOption = new Option<int>( name: "--delay", description: "Delay between lines, specified as milliseconds per character in a line.", getDefaultValue: () => 42); var fgcolorOption = new Option<ConsoleColor>( name: "--fgcolor", description: "Foreground color of text displayed on the console.", getDefaultValue: () => ConsoleColor.White); var lightModeOption = new Option<bool>( name: "--light-mode", description: "Background color of text displayed on the console: default is black, light mode is white.");
ルート コマンドを作成した行の後、
--file
オプションを追加した行を削除します。 ここで削除しているのは、新しいサブコマンドに追加するためです。var rootCommand = new RootCommand("Sample app for System.CommandLine"); //rootCommand.AddOption(fileOption);
ルート コマンドを作成した行の後に、
read
サブコマンドを作成します。 このサブコマンドにオプションを追加し、そのサブコマンドをルート コマンドに追加します。var readCommand = new Command("read", "Read and display the file.") { fileOption, delayOption, fgcolorOption, lightModeOption }; rootCommand.AddCommand(readCommand);
新しいサブコマンドについて、
SetHandler
コードを次のSetHandler
コードに置き換えます。readCommand.SetHandler(async (file, delay, fgcolor, lightMode) => { await ReadFile(file!, delay, fgcolor, lightMode); }, fileOption, delayOption, fgcolorOption, lightModeOption);
ルート コマンドにハンドラーが不要になったため、ルート コマンドで
SetHandler
を呼び出す必要がなくなりました。 通常、コマンドにサブコマンドがある場合、コマンドライン アプリケーションを起動するときにサブコマンドのいずれかを指定する必要があります。ReadFile
ハンドラー メソッドを次のコードに置き換えます。internal static async Task ReadFile( FileInfo file, int delay, ConsoleColor fgColor, bool lightMode) { Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black; Console.ForegroundColor = fgColor; List<string> lines = File.ReadLines(file.FullName).ToList(); foreach (string line in lines) { Console.WriteLine(line); await Task.Delay(delay * line.Length); }; }
これで、アプリは次のようになります。
using System.CommandLine;
namespace scl;
class Program
{
static async Task<int> Main(string[] args)
{
var fileOption = new Option<FileInfo?>(
name: "--file",
description: "The file to read and display on the console.");
var delayOption = new Option<int>(
name: "--delay",
description: "Delay between lines, specified as milliseconds per character in a line.",
getDefaultValue: () => 42);
var fgcolorOption = new Option<ConsoleColor>(
name: "--fgcolor",
description: "Foreground color of text displayed on the console.",
getDefaultValue: () => ConsoleColor.White);
var lightModeOption = new Option<bool>(
name: "--light-mode",
description: "Background color of text displayed on the console: default is black, light mode is white.");
var rootCommand = new RootCommand("Sample app for System.CommandLine");
//rootCommand.AddOption(fileOption);
var readCommand = new Command("read", "Read and display the file.")
{
fileOption,
delayOption,
fgcolorOption,
lightModeOption
};
rootCommand.AddCommand(readCommand);
readCommand.SetHandler(async (file, delay, fgcolor, lightMode) =>
{
await ReadFile(file!, delay, fgcolor, lightMode);
},
fileOption, delayOption, fgcolorOption, lightModeOption);
return rootCommand.InvokeAsync(args).Result;
}
internal static async Task ReadFile(
FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
{
Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
Console.ForegroundColor = fgColor;
List<string> lines = File.ReadLines(file.FullName).ToList();
foreach (string line in lines)
{
Console.WriteLine(line);
await Task.Delay(delay * line.Length);
};
}
}
新しいサブコマンドをテストする
サブコマンドを指定せずにアプリを実行しようとすると、エラー メッセージの後に、使用できるサブコマンドを指定するヘルプ メッセージが表示されるようになりました。
scl --file sampleQuotes.txt
'--file' was not matched. Did you mean one of the following?
--help
Required command was not provided.
Unrecognized command or argument '--file'.
Unrecognized command or argument 'sampleQuotes.txt'.
Description:
Sample app for System.CommandLine
Usage:
scl [command] [options]
Options:
--version Show version information
-?, -h, --help Show help and usage information
Commands:
read Read and display the file.
サブコマンド read
のヘルプ テキストは、4 つのオプションを使用できることを示しています。 列挙型の有効な値が示されています。
scl read -h
Description:
Read and display the file.
Usage:
scl read [options]
Options:
--file <file> The file to read and display on the console.
--delay <delay> Delay between lines, specified as milliseconds per
character in a line. [default: 42]
--fgcolor Foreground color of text displayed on the console.
<Black|Blue|Cyan|DarkBlue|DarkCyan|DarkGray|DarkGreen|Dark [default: White]
Magenta|DarkRed|DarkYellow|Gray|Green|Magenta|Red|White|Ye
llow>
--light-mode Background color of text displayed on the console:
default is black, light mode is white.
-?, -h, --help Show help and usage information
--file
オプションのみを指定してサブコマンド read
を実行すると、他の 3 つのオプションの既定値を取得できます。
scl read --file sampleQuotes.txt
1 文字あたり 42 ミリ秒の既定の遅延が生じるため、読み上げ速度は遅くなります。 --delay
を低い数値に設定すると、速度を上げることができます。
scl read --file sampleQuotes.txt --delay 0
--fgcolor
と --light-mode
でテキストの色を設定できます。
scl read --file sampleQuotes.txt --fgcolor red --light-mode
--delay
に無効な値を指定すると、エラー メッセージを受け取ります。
scl read --file sampleQuotes.txt --delay forty-two
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.
--file
に無効な値を指定すると、例外を受け取ります。
scl read --file nofile
Unhandled exception: System.IO.FileNotFoundException:
Could not find file 'C:\bin\Debug\net6.0\nofile'.
サブコマンドとカスタム検証を追加する
このセクションでは、アプリの最終バージョンを作成します。 完成すると、アプリのコマンドとオプションは次のようになります。
--file
というグローバル* オプションがあるルート コマンドquotes
コマンド--delay
、--fgcolor
、--light-mode
というオプションがあるread
コマンドquote
、byline
という引数があるadd
コマンド--search-terms
というオプションがあるdelete
コマンド
* グローバル オプションは、割り当てられたコマンドと、再帰的にそのサブコマンドすべてに使用できます。
使用できる各コマンドを、そのオプションと引数を指定して呼び出すコマンド ライン入力の例を次に示します。
scl quotes read --file sampleQuotes.txt --delay 40 --fgcolor red --light-mode
scl quotes add "Hello world!" "Nancy Davolio"
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
Program.cs で、
--file
オプションを作成するコードを次のコードに置き換えます。var fileOption = new Option<FileInfo?>( name: "--file", description: "An option whose argument is parsed as a FileInfo", isDefault: true, parseArgument: result => { if (result.Tokens.Count == 0) { return new FileInfo("sampleQuotes.txt"); } string? filePath = result.Tokens.Single().Value; if (!File.Exists(filePath)) { result.ErrorMessage = "File does not exist"; return null; } else { return new FileInfo(filePath); } });
このコードでは、ParseArgument<T> を使ってカスタムの解析、検証、エラー処理を行います。
このコードを使用しない場合、不足しているファイルと共に、例外、スタック トレースが報告されます。 このコードでは、指定されたエラー メッセージだけが表示されます。
このコードでは、既定値も指定しています。そのため、
isDefault
の設定はtrue
です。isDefault
をtrue
に設定しない場合、--file
に入力を指定していないときにparseArgument
のデリゲートが呼び出されません。lightModeOption
を作成するコードの後に、add
とdelete
の各コマンドのオプションと引数を追加します。var searchTermsOption = new Option<string[]>( name: "--search-terms", description: "Strings to search for when deleting entries.") { IsRequired = true, AllowMultipleArgumentsPerToken = true }; var quoteArgument = new Argument<string>( name: "quote", description: "Text of quote."); var bylineArgument = new Argument<string>( name: "byline", description: "Byline of quote.");
AllowMultipleArgumentsPerToken の設定を指定すると、一覧の要素を指定するときに、1 つ目以外の
--search-terms
のオプション名を省略できます。 これにより、次のコマンドライン入力の例が同等になります。scl quotes delete --search-terms David "You can do" scl quotes delete --search-terms David --search-terms "You can do"
ルート コマンドと
read
コマンドを作成するコードを次のコードに置き換えます。var rootCommand = new RootCommand("Sample app for System.CommandLine"); rootCommand.AddGlobalOption(fileOption); var quotesCommand = new Command("quotes", "Work with a file that contains quotes."); rootCommand.AddCommand(quotesCommand); var readCommand = new Command("read", "Read and display the file.") { delayOption, fgcolorOption, lightModeOption }; quotesCommand.AddCommand(readCommand); var deleteCommand = new Command("delete", "Delete lines from the file."); deleteCommand.AddOption(searchTermsOption); quotesCommand.AddCommand(deleteCommand); var addCommand = new Command("add", "Add an entry to the file."); addCommand.AddArgument(quoteArgument); addCommand.AddArgument(bylineArgument); addCommand.AddAlias("insert"); quotesCommand.AddCommand(addCommand);
このコードにより、次の変更が行われます。
read
コマンドから--file
オプションを削除します。ルート コマンドにグローバル オプションとして
--file
オプションを追加します。quotes
コマンドを作成し、ルート コマンドに追加します。read
コマンドをルート コマンドにではなくquotes
コマンドに追加します。add
とdelete
の各コマンドを作成し、quotes
コマンドに追加します。
その結果、次のようなコマンド階層になります。
- ルート コマンド
quotes
read
add
delete
親コマンド (
quotes
) で領域またはグループを指定し、その子コマンド (read
、add
、delete
) がアクションという推奨パターンをアプリに実装しました。グローバル オプションはコマンドに適用され、サブコマンドにも再帰的に適用されます。
--file
はルート コマンド上にあるので、アプリのすべてのサブコマンドで自動的に使用できるようになります。SetHandler
コードの後に、新しいサブコマンドのための新しいSetHandler
のコードを追加します。deleteCommand.SetHandler((file, searchTerms) => { DeleteFromFile(file!, searchTerms); }, fileOption, searchTermsOption); addCommand.SetHandler((file, quote, byline) => { AddToFile(file!, quote, byline); }, fileOption, quoteArgument, bylineArgument);
サブコマンド
quotes
はリーフ コマンドではないため、ハンドラーがありません。 サブコマンドread
、add
、delete
はquotes
以下のリーフ コマンドであり、そのそれぞれに対してSetHandler
が呼び出されます。add
とdelete
のハンドラーを追加します。internal static void DeleteFromFile(FileInfo file, string[] searchTerms) { Console.WriteLine("Deleting from file"); File.WriteAllLines( file.FullName, File.ReadLines(file.FullName) .Where(line => searchTerms.All(s => !line.Contains(s))).ToList()); } internal static void AddToFile(FileInfo file, string quote, string byline) { Console.WriteLine("Adding to file"); using StreamWriter? writer = file.AppendText(); writer.WriteLine($"{Environment.NewLine}{Environment.NewLine}{quote}"); writer.WriteLine($"{Environment.NewLine}-{byline}"); writer.Flush(); }
完成したアプリは次のようになります。
using System.CommandLine;
namespace scl;
class Program
{
static async Task<int> Main(string[] args)
{
var fileOption = new Option<FileInfo?>(
name: "--file",
description: "An option whose argument is parsed as a FileInfo",
isDefault: true,
parseArgument: result =>
{
if (result.Tokens.Count == 0)
{
return new FileInfo("sampleQuotes.txt");
}
string? filePath = result.Tokens.Single().Value;
if (!File.Exists(filePath))
{
result.ErrorMessage = "File does not exist";
return null;
}
else
{
return new FileInfo(filePath);
}
});
var delayOption = new Option<int>(
name: "--delay",
description: "Delay between lines, specified as milliseconds per character in a line.",
getDefaultValue: () => 42);
var fgcolorOption = new Option<ConsoleColor>(
name: "--fgcolor",
description: "Foreground color of text displayed on the console.",
getDefaultValue: () => ConsoleColor.White);
var lightModeOption = new Option<bool>(
name: "--light-mode",
description: "Background color of text displayed on the console: default is black, light mode is white.");
var searchTermsOption = new Option<string[]>(
name: "--search-terms",
description: "Strings to search for when deleting entries.")
{ IsRequired = true, AllowMultipleArgumentsPerToken = true };
var quoteArgument = new Argument<string>(
name: "quote",
description: "Text of quote.");
var bylineArgument = new Argument<string>(
name: "byline",
description: "Byline of quote.");
var rootCommand = new RootCommand("Sample app for System.CommandLine");
rootCommand.AddGlobalOption(fileOption);
var quotesCommand = new Command("quotes", "Work with a file that contains quotes.");
rootCommand.AddCommand(quotesCommand);
var readCommand = new Command("read", "Read and display the file.")
{
delayOption,
fgcolorOption,
lightModeOption
};
quotesCommand.AddCommand(readCommand);
var deleteCommand = new Command("delete", "Delete lines from the file.");
deleteCommand.AddOption(searchTermsOption);
quotesCommand.AddCommand(deleteCommand);
var addCommand = new Command("add", "Add an entry to the file.");
addCommand.AddArgument(quoteArgument);
addCommand.AddArgument(bylineArgument);
addCommand.AddAlias("insert");
quotesCommand.AddCommand(addCommand);
readCommand.SetHandler(async (file, delay, fgcolor, lightMode) =>
{
await ReadFile(file!, delay, fgcolor, lightMode);
},
fileOption, delayOption, fgcolorOption, lightModeOption);
deleteCommand.SetHandler((file, searchTerms) =>
{
DeleteFromFile(file!, searchTerms);
},
fileOption, searchTermsOption);
addCommand.SetHandler((file, quote, byline) =>
{
AddToFile(file!, quote, byline);
},
fileOption, quoteArgument, bylineArgument);
return await rootCommand.InvokeAsync(args);
}
internal static async Task ReadFile(
FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
{
Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
Console.ForegroundColor = fgColor;
var lines = File.ReadLines(file.FullName).ToList();
foreach (string line in lines)
{
Console.WriteLine(line);
await Task.Delay(delay * line.Length);
};
}
internal static void DeleteFromFile(FileInfo file, string[] searchTerms)
{
Console.WriteLine("Deleting from file");
File.WriteAllLines(
file.FullName, File.ReadLines(file.FullName)
.Where(line => searchTerms.All(s => !line.Contains(s))).ToList());
}
internal static void AddToFile(FileInfo file, string quote, string byline)
{
Console.WriteLine("Adding to file");
using StreamWriter? writer = file.AppendText();
writer.WriteLine($"{Environment.NewLine}{Environment.NewLine}{quote}");
writer.WriteLine($"{Environment.NewLine}-{byline}");
writer.Flush();
}
}
プロジェクトをビルドして、次のコマンドを試してみてください。
read
コマンドを使って存在しないファイルを --file
に送信すると、例外とスタック トレースではなく、エラー メッセージを受け取ります。
scl quotes read --file nofile
File does not exist
サブコマンド quotes
を実行しようとすると、read
、add
、または delete
を使うように指示するメッセージを受け取ります。
scl quotes
Required command was not provided.
Description:
Work with a file that contains quotes.
Usage:
scl quotes [command] [options]
Options:
--file <file> An option whose argument is parsed as a FileInfo [default: sampleQuotes.txt]
-?, -h, --help Show help and usage information
Commands:
read Read and display the file.
delete Delete lines from the file.
add, insert <quote> <byline> Add an entry to the file.
サブコマンド add
を実行し、テキスト ファイルの末尾に追加されたテキストを確認します。
scl quotes add "Hello world!" "Nancy Davolio"
ファイルの先頭から検索文字列を指定してサブコマンド delete
を実行し、テキスト ファイルの先頭を見て、テキストが削除された場所を確認します。
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
注意
bin/debug/net6.0 フォルダーで実行している場合、そのフォルダーには add
と delete
の各コマンドによる変更が加えられたファイルがあります。 プロジェクト フォルダー内のファイルのコピーは変更されません。
次の手順
このチュートリアルでは、System.CommandLine
を使う簡単なコマンドライン アプリを作成しました。 ライブラリの詳細については、System.CommandLine の概要に関するページを参照してください。
.NET