教學課程:開始使用 System.CommandLine
重要
System.CommandLine
目前為預覽版,本文件適用於版本 2.0 搶鮮版 (Beta) 4。
部分資訊與發行前產品相關,發行前可能會大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。
此教學課程說明如何建立使用 System.CommandLine
程式庫的 .NET 命令列應用程式。 首先,您會建立具有一個選項的簡單根命令。 然後,您將新增至該基底,建立更複雜的應用程式,其中包含多個子命令與每個命令的不同選項。
在本教學課程中,您會了解如何:
- 建立命令、選項與引數。
- 指定選項的預設值。
- 將選項與引數指派給命令。
- 以遞迴方式將選項指派給命令下的所有子命令。
- 使用多個層級的巢狀子命令。
- 建立命令與選項的別名。
- 使用
string
、string[]
、int
、bool
、FileInfo
與列舉選項類型。 - 將選項值繫結至命令處理常式程式碼。
- 使用自訂程式碼剖析及驗證選項。
必要條件
- 程式碼編輯器,例如使用具有 C# 延伸模組 (英文) 的 Visual Studio Code (英文)。
- .NET 6 SDK。
Or
- 已安裝 .NET 桌面開發工作負載的 Visual Studio 2022。
建立應用程式
建立名為 "scl" 的 .NET 6 主控台應用程式專案。
為專案建立名為 scl 的資料夾,然後在此新資料夾中開啟命令提示字元。
執行以下命令:
dotnet new console --framework net6.0
安裝 System.CommandLine 套件
執行以下命令:
dotnet add package System.CommandLine --prerelease
必須有
--prerelease
選項,因為程式庫仍為搶鮮版 (Beta)。
以下列程式碼取代 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)); } }
上述程式碼:
建立名為
--file
之類型 FileInfo 的選項,並將其指派給根命令: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 Preview 中,您可以執行
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
新增子命令與選項
在本節中,您可:
- 建立更多選項。
- 建立子命令。
- 將新選項指派給新的子命令。
新的選項可讓您設定前景與背景文字色彩,以及朗讀速度。 這些功能將用於讀取來自 Teleprompter 主控台應用程式教學課程的引號集合。
請從 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
的說明文字會顯示有四個選項可供使用。 其會顯示列舉的有效值。
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
,並取得其他三個選項的預設值。
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\net6.0\nofile'.
新增子命令與自訂驗證
本節會建立應用程式的最終版本。 完成之後,應用程式將會有下列命令與選項:
- 具有名為
--file
之全域* 選項的根命令quotes
命令read
命令,其中包含名為--delay
、--fgcolor
與--light-mode
的選項add
命令,其中包含名為quote
與byline
的引數- 具有名為
--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 設定可讓您在第一個選項名稱後指定清單中的元素時,省略
--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 概觀。