本教學課程示範如何建立一個使用System.CommandLine類別庫的 .NET 命令列應用程式。 您一開始會建立具有一個選項的簡單根命令。 然後,您將建置在該基底上,建立更複雜的應用程式,其中包含多個子命令,以及每個命令的不同選項。
在本教學課程中,您將瞭解如何:
- 建立命令、選項和自變數。
- 指定選項的預設值。
- 將選項和自變數指派給命令。
- 以遞歸方式將選項指派給命令下的所有子命令。
- 使用多層巢狀的子命令。
- 建立命令和選項的別名。
- 使用
string、string[]、int、bool、FileInfo和 列舉選項類型。 - 讀取命令動作程式碼中的選項值。
- 使用自定義程式代碼來剖析和驗證選項。
先決條件
- 最新 .NET SDK
- Visual Studio Code 編輯器
- C# 開發套件
或
- Visual Studio 2022 已安裝 .NET 桌面開發工作負載。
建立應用程式
建立名為 「scl」 的 .NET 9 控制台應用程式專案。
為專案建立名為 scl 的資料夾,然後在新資料夾中開啟命令提示字元。
執行下列命令:
dotnet new console --framework net9.0
安裝 System.CommandLine 套件
執行下列命令:
dotnet add package System.CommandLine --prerelease或者,在 .NET 10 及更高版本中:
dotnet package add System.CommandLine --prerelease--prerelease選項是必要的,因為連結庫仍在 Beta 中。
剖析參數
使用下列程式碼取代 Program.cs 的內容:
using System.CommandLine; using System.CommandLine.Parsing; namespace scl; class Program { static int Main(string[] args) { Option<FileInfo> fileOption = new("--file") { Description = "The file to read and display on the console." }; RootCommand rootCommand = new("Sample app for System.CommandLine"); rootCommand.Options.Add(fileOption); ParseResult parseResult = rootCommand.Parse(args); if (parseResult.Errors.Count == 0 && parseResult.GetValue(fileOption) is FileInfo parsedFile) { ReadFile(parsedFile); return 0; } foreach (ParseError parseError in parseResult.Errors) { Console.Error.WriteLine(parseError.Message); } return 1; } static void ReadFile(FileInfo file) { foreach (string line in File.ReadLines(file.FullName)) { Console.WriteLine(line); } } }
上述 程式碼:
Option<FileInfo> fileOption = new("--file")
{
Description = "The file to read and display on the console."
};
RootCommand rootCommand = new("Sample app for System.CommandLine");
rootCommand.Options.Add(fileOption);
- 剖析
args並檢查是否有為--file選項提供任何值。 如果是,它會使用剖析的值呼叫ReadFile方法,並傳0回結束代碼:
ParseResult parseResult = rootCommand.Parse(args);
if (parseResult.Errors.Count == 0 && parseResult.GetValue(fileOption) is FileInfo parsedFile)
{
ReadFile(parsedFile);
return 0;
}
- 如果未提供
--file任何值,則會列印可用的剖析錯誤,並傳1回結束代碼:
foreach (ParseError parseError in parseResult.Errors)
{
Console.Error.WriteLine(parseError.Message);
}
return 1;
- 方法
ReadFile會讀取指定的檔案,並在控制台上顯示其內容:
static void ReadFile(FileInfo file)
{
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
}
}
測試應用程式
您可以在開發命令列應用程式時,使用下列任何一種方式進行測試:
dotnet build執行命令,然後在 scl/bin/Debug/net9.0 資料夾中開啟命令提示字元以執行可執行檔:dotnet build cd bin/Debug/net9.0 scl --file scl.runtimeconfig.json使用
dotnet run並將選項值傳遞給應用程式,而不是傳遞給run指令,請將選項值放在--之後,如下列範例所示:dotnet run -- --file bin/Debug/net9.0/scl.runtimeconfig.json
本教學課程假設您使用的是這些選項中的第一個。
當您執行應用程式時,它會顯示 選項所 --file 指定的檔案內容。
{
"runtimeOptions": {
"tfm": "net9.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
}
}
}
但是,如果您要求它使用 --help 顯示說明,會發生什麼事? 由於應用程式尚未處理未提供 --file 且沒有剖析錯誤的情況,因此不會在主控台列印任何內容。
剖析參數並呼叫 ParseResult
System.CommandLine 可讓您指定動作,在成功解析命令、指令或選項時執行。 動作是接受 System.CommandLine.ParseResult 參數並傳回 int 退出代碼的委託函式(異步動作也可使用)。 結束代碼是由 System.CommandLine.Parsing.ParseResult.Invoke 方法傳回,可用來指出命令是否成功執行。
使用下列程式碼取代 Program.cs 的內容:
using System.CommandLine; namespace scl; class Program { static int Main(string[] args) { Option<FileInfo> fileOption = new("--file") { Description = "The file to read and display on the console." }; RootCommand rootCommand = new("Sample app for System.CommandLine"); rootCommand.Options.Add(fileOption); rootCommand.SetAction(parseResult => { FileInfo parsedFile = parseResult.GetValue(fileOption); ReadFile(parsedFile); return 0; }); ParseResult parseResult = rootCommand.Parse(args); return parseResult.Invoke(); } static void ReadFile(FileInfo file) { foreach (string line in File.ReadLines(file.FullName)) { Console.WriteLine(line); } } }
上述 程式碼:
指定 ,
ReadFile這是叫用根命令時要呼叫的方法:rootCommand.SetAction(parseResult => { FileInfo parsedFile = parseResult.GetValue(fileOption); ReadFile(parsedFile); return 0; });剖析
args並 叫用 結果:ParseResult parseResult = rootCommand.Parse(args); return parseResult.Invoke();
當您執行應用程式時,它會顯示 選項所 --file 指定的檔案內容。
如果您要求它藉由提供 --help來顯示說明,會發生什麼事?
scl --help
以下是印出的結果:
Description:
Sample app for System.CommandLine
Usage:
scl [options]
Options:
-?, -h, --help Show help and usage information
--version Show version information
--file The file to read and display on the conso
RootCommand 根據預設,提供 [說明] 選項、[ 版本] 選項和 [建議] 指示詞。 方法 ParseResult.Invoke(InvocationConfiguration) 負責叫用已剖析符號的動作。 它可以是針對 命令明確定義的動作,或 針對所System.CommandLine定義的System.CommandLine.Help.HelpOption說明動作。 此外,當它偵測到任何剖析錯誤時,它會將它們列印到標準錯誤、列印有助於標準輸出,並傳回 1 作為結束代碼:
scl --invalid bla
Unrecognized command or argument '--invalid'.
Unrecognized command or argument 'bla'.
新增子命令和選項
在本節中,您會:
- 建立更多選項。
- 建立子命令。
- 將新選項指派給新的子命令。
新的選項可讓您設定前景和背景文字色彩和讀出速度。 這些功能將用於讀取Teleprompter 控制台應用程式教學中的語錄集合。
將此範例的 gitHub 存放庫sampleQuotes.txt檔案複製到您的項目目錄中。 如需如何下載檔案的資訊,請參閱 範例和教學課程中的指示。
開啟項目檔,並在結尾
<ItemGroup>標記之前新增</Project>元素:<ItemGroup> <Content Include="sampleQuotes.txt"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup>新增此標記會導致當您建置應用程式時,文本檔複製到 bin/debug/net9.0 資料夾。 因此,當您在該資料夾中執行可執行檔時,您可以依名稱存取檔案,而不需要指定資料夾路徑。
在 Program.cs中,在建立
--file選項的程式代碼之後,建立選項來控制讀出速度和文字色彩:Option<int> delayOption = new("--delay") { Description = "Delay between lines, specified as milliseconds per character in a line.", DefaultValueFactory = parseResult => 42 }; Option<ConsoleColor> fgcolorOption = new("--fgcolor") { Description = "Foreground color of text displayed on the console.", DefaultValueFactory = parseResult => ConsoleColor.White }; Option<bool> lightModeOption = new("--light-mode") { Description = "Background color of text displayed on the console: default is black, light mode is white." };在建立根命令的行之後,刪除將
--file選項新增至它的程序代碼。 您在這裡移除它,因為您會將其新增至新的子命令。在建立根命令的行之後,建立
read子命令。 將選項新增至這個子命令(使用集合初始化表達式語法而非Options屬性),並將子命令新增至根命令。Command readCommand = new("read", "Read and display the file.") { fileOption, delayOption, fgcolorOption, lightModeOption }; rootCommand.Subcommands.Add(readCommand);將
SetAction代碼取代為下列新子命令的SetAction代碼:readCommand.SetAction(parseResult => ReadFile( parseResult.GetValue(fileOption), parseResult.GetValue(delayOption), parseResult.GetValue(fgcolorOption), parseResult.GetValue(lightModeOption)));您不再在根命令上呼叫
SetAction,因為根命令不再需要動作。 當命令具有子命令時,您通常需要在叫用命令行應用程式時指定其中一個子命令。請使用以下程式碼取代
ReadFileaction 方法:internal static void ReadFile(FileInfo file, int delay, ConsoleColor fgColor, bool lightMode) { Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black; Console.ForegroundColor = fgColor; foreach (string line in File.ReadLines(file.FullName)) { Console.WriteLine(line); Thread.Sleep(TimeSpan.FromMilliseconds(delay * line.Length)); } }
應用程式現在看起來像這樣:
using System.CommandLine;
namespace scl;
class Program
{
static int Main(string[] args)
{
Option<FileInfo> fileOption = new("--file")
{
Description = "The file to read and display on the console."
};
Option<int> delayOption = new("--delay")
{
Description = "Delay between lines, specified as milliseconds per character in a line.",
DefaultValueFactory = parseResult => 42
};
Option<ConsoleColor> fgcolorOption = new("--fgcolor")
{
Description = "Foreground color of text displayed on the console.",
DefaultValueFactory = parseResult => ConsoleColor.White
};
Option<bool> lightModeOption = new("--light-mode")
{
Description = "Background color of text displayed on the console: default is black, light mode is white."
};
RootCommand rootCommand = new("Sample app for System.CommandLine");
Command readCommand = new("read", "Read and display the file.")
{
fileOption,
delayOption,
fgcolorOption,
lightModeOption
};
rootCommand.Subcommands.Add(readCommand);
readCommand.SetAction(parseResult => ReadFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(delayOption),
parseResult.GetValue(fgcolorOption),
parseResult.GetValue(lightModeOption)));
return rootCommand.Parse(args).Invoke();
}
internal static void ReadFile(FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
{
Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
Console.ForegroundColor = fgColor;
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
Thread.Sleep(TimeSpan.FromMilliseconds(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:
-?, -h, --help Show help and usage information
--version Show version information
Commands:
read Read and display the file.
子命令 read 的說明文字顯示有四個選項可供使用。 它顯示了列舉型別的有效值。
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
執行子命令 read 並且只指定 --file 選項,即可取得其他三個選項的預設值。
scl read --file sampleQuotes.txt
每個字元默認延遲 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\net9.0\nofile''.
File name: 'C:\bin\Debug\net9.0\nofile''
新增子命令和自定義驗證
本節會建立應用程式的最終版本。 完成後,應用程式會有下列命令和選項:
- 具有名為
--file的遞迴選項的 root 命令-
quotes命令-
read命令,其中包含名為--delay、--fgcolor和 的選項--light-mode -
add命令,具有名為quote和byline的自變數 -
delete具有名為--search-terms的選項命令
-
-
* 遞歸選項可供指派給的命令使用,並以遞歸方式遞歸至其所有子命令。
以下是使用其選項和自變數叫用每個可用命令的命令列輸入範例:
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選項的程式代碼:Option<FileInfo> fileOption = new("--file") { Description = "An option whose argument is parsed as a FileInfo", Required = true, DefaultValueFactory = result => { if (result.Tokens.Count == 0) { return new FileInfo("sampleQuotes.txt"); } string filePath = result.Tokens.Single().Value; if (!File.Exists(filePath)) { result.AddError("File does not exist"); return null; } else { return new FileInfo(filePath); } } };此程式代碼會使用
System.CommandLine.Parsing.ArgumentResult來提供自定義剖析、驗證和錯誤處理。如果沒有此程式代碼,則會回報遺漏的檔案,並具有例外狀況和堆棧追蹤。 使用這個程式代碼時,只會顯示指定的錯誤訊息。
此程式代碼也會指定預設值,這就是為什麼它會設定
DefaultValueFactory為自定義剖析方法的原因。在建立
lightModeOption的程式代碼之後,新增add和delete命令的選項和自變數:Option<string[]> searchTermsOption = new("--search-terms") { Description = "Strings to search for when deleting entries.", Required = true, AllowMultipleArgumentsPerToken = true }; Argument<string> quoteArgument = new("quote") { Description = "Text of quote." }; Argument<string> bylineArgument = new("byline") { Description = "Byline of quote." };設定
xref:System.CommandLine.Option.AllowMultipleArgumentsPerToken可讓您在第一個選項名稱之後指定清單中的元素時省略--search-terms選項名稱。 它使以下命令列輸入的範例等同:scl quotes delete --search-terms David "You can do" scl quotes delete --search-terms David --search-terms "You can do"以下列程式代碼取代建立根命令和
read命令的程式代碼:RootCommand rootCommand = new("Sample app for System.CommandLine"); fileOption.Recursive = true; rootCommand.Options.Add(fileOption); Command quotesCommand = new("quotes", "Work with a file that contains quotes."); rootCommand.Subcommands.Add(quotesCommand); Command readCommand = new("read", "Read and display the file.") { delayOption, fgcolorOption, lightModeOption }; quotesCommand.Subcommands.Add(readCommand); Command deleteCommand = new("delete", "Delete lines from the file."); deleteCommand.Options.Add(searchTermsOption); quotesCommand.Subcommands.Add(deleteCommand); Command addCommand = new("add", "Add an entry to the file."); addCommand.Arguments.Add(quoteArgument); addCommand.Arguments.Add(bylineArgument); addCommand.Aliases.Add("insert"); quotesCommand.Subcommands.Add(addCommand);此程式代碼會進行下列變更:
從
--file命令中移除read選項。將
--file選項新增為遞歸選項至根命令。建立
quotes命令並將它新增至根命令。將
read命令新增至quotes命令,而不是新增至根命令。創建
add和delete命令,並將它們新增到quotes命令中。
結果是下列命令階層:
- 根命令
quotesreadadddelete
應用程式現在會實作建議模式,其中父命令 (
quotes) 指定區域或群組,以及其子命令 (read、adddelete、 ) 是動作。遞歸選項會套用至命令,並以遞歸方式套用至子命令。 由於
--file位於根命令上,因此會自動在應用程式的所有子命令中使用。在
SetAction代碼之後,為新的子命令新增新的SetAction代碼:deleteCommand.SetAction(parseResult => DeleteFromFile( parseResult.GetValue(fileOption), parseResult.GetValue(searchTermsOption))); addCommand.SetAction(parseResult => AddToFile( parseResult.GetValue(fileOption), parseResult.GetValue(quoteArgument), parseResult.GetValue(bylineArgument)) );子命令
quotes沒有動作,因為它不是分葉命令。 子命令read、add和delete是quotes下的末端命令,而且SetAction會針對它們每一個進行呼叫。新增
add和delete的動作。internal static void DeleteFromFile(FileInfo file, string[] searchTerms) { Console.WriteLine("Deleting from file"); var lines = File.ReadLines(file.FullName).Where(line => searchTerms.All(s => !line.Contains(s))); File.WriteAllLines(file.FullName, lines); } 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}"); }
已完成的應用程式看起來像這樣:
using System.CommandLine;
namespace scl;
class Program
{
static int Main(string[] args)
{
Option<FileInfo> fileOption = new("--file")
{
Description = "An option whose argument is parsed as a FileInfo",
Required = true,
DefaultValueFactory = result =>
{
if (result.Tokens.Count == 0)
{
return new FileInfo("sampleQuotes.txt");
}
string filePath = result.Tokens.Single().Value;
if (!File.Exists(filePath))
{
result.AddError("File does not exist");
return null;
}
else
{
return new FileInfo(filePath);
}
}
};
Option<int> delayOption = new("--delay")
{
Description = "Delay between lines, specified as milliseconds per character in a line.",
DefaultValueFactory = parseResult => 42
};
Option<ConsoleColor> fgcolorOption = new("--fgcolor")
{
Description = "Foreground color of text displayed on the console.",
DefaultValueFactory = parseResult => ConsoleColor.White
};
Option<bool> lightModeOption = new("--light-mode")
{
Description = "Background color of text displayed on the console: default is black, light mode is white."
};
Option<string[]> searchTermsOption = new("--search-terms")
{
Description = "Strings to search for when deleting entries.",
Required = true,
AllowMultipleArgumentsPerToken = true
};
Argument<string> quoteArgument = new("quote")
{
Description = "Text of quote."
};
Argument<string> bylineArgument = new("byline")
{
Description = "Byline of quote."
};
RootCommand rootCommand = new("Sample app for System.CommandLine");
fileOption.Recursive = true;
rootCommand.Options.Add(fileOption);
Command quotesCommand = new("quotes", "Work with a file that contains quotes.");
rootCommand.Subcommands.Add(quotesCommand);
Command readCommand = new("read", "Read and display the file.")
{
delayOption,
fgcolorOption,
lightModeOption
};
quotesCommand.Subcommands.Add(readCommand);
Command deleteCommand = new("delete", "Delete lines from the file.");
deleteCommand.Options.Add(searchTermsOption);
quotesCommand.Subcommands.Add(deleteCommand);
Command addCommand = new("add", "Add an entry to the file.");
addCommand.Arguments.Add(quoteArgument);
addCommand.Arguments.Add(bylineArgument);
addCommand.Aliases.Add("insert");
quotesCommand.Subcommands.Add(addCommand);
readCommand.SetAction(parseResult => ReadFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(delayOption),
parseResult.GetValue(fgcolorOption),
parseResult.GetValue(lightModeOption)));
deleteCommand.SetAction(parseResult => DeleteFromFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(searchTermsOption)));
addCommand.SetAction(parseResult => AddToFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(quoteArgument),
parseResult.GetValue(bylineArgument))
);
return rootCommand.Parse(args).Invoke();
}
internal static void ReadFile(FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
{
Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
Console.ForegroundColor = fgColor;
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
Thread.Sleep(TimeSpan.FromMilliseconds(delay * line.Length));
}
}
internal static void DeleteFromFile(FileInfo file, string[] searchTerms)
{
Console.WriteLine("Deleting from file");
var lines = File.ReadLines(file.FullName).Where(line => searchTerms.All(s => !line.Contains(s)));
File.WriteAllLines(file.FullName, lines);
}
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}");
}
}
建置項目,然後嘗試下列命令。
使用 --file 命令將不存在的檔案提交至 read,您會收到錯誤訊息,而不是例外狀況和堆疊追蹤:
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/net9.0 資料夾中執行,這個資料夾就是您可以找到有來自 add 和 delete 命令變更的檔案的位置。 項目資料夾中的檔案複本保持不變。
後續步驟
在本教學課程中,您已建立使用 System.CommandLine的簡單命令行應用程式。 若要深入了解函式庫,請參閱 System.CommandLine 概觀。