Samouczek: wprowadzenie do System.CommandLine
Ważne
System.CommandLine
jest obecnie w wersji zapoznawczej, a ta dokumentacja dotyczy wersji 2.0 beta 4.
Niektóre informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany przed jego wydaniem. Firma Microsoft nie udziela żadnych gwarancji, wyraźnych ani domniemanych, w odniesieniu do podanych tutaj informacji.
W tym samouczku pokazano, jak utworzyć aplikację wiersza polecenia platformy .NET korzystającą z biblioteki System.CommandLine
. Zaczniesz od utworzenia prostego polecenia głównego, które ma jedną opcję. Następnie dodasz do tej bazy, tworząc bardziej złożoną aplikację zawierającą wiele podpuleceń oraz różne opcje dla każdego polecenia.
Z tego samouczka dowiesz się, jak wykonywać następujące działania:
- Utwórz polecenia, opcje i argumenty.
- Określ wartości domyślne opcji.
- Przypisz opcje i argumenty do poleceń.
- Przypisz opcję rekursywnie do wszystkich podpoleceń w poleceniu.
- Pracować z wieloma poziomami zagnieżdżonych poleceń podrzędnych.
- Utwórz aliasy dla poleceń i opcji.
- Praca z typami opcji
string
,string[]
,int
,bool
,FileInfo
i wyliczeniowymi. - Powiąż wartości opcji z kodem programu obsługi poleceń.
- Użyj niestandardowego kodu do analizowania i sprawdzania poprawności opcji.
Wymagania wstępne
- Najnowsza wersja zestawu .NET SDK
- Edytor programu Visual Studio Code
- Zestaw deweloperski C#
lub
- programu Visual Studio 2022 z zainstalowanym pakietem do tworzenia aplikacji klasycznych .NET .
Tworzenie aplikacji
Utwórz projekt aplikacji konsolowej platformy .NET 6 o nazwie "scl".
Utwórz folder o nazwie scl dla projektu, a następnie otwórz wiersz polecenia w nowym folderze.
Uruchom następujące polecenie:
dotnet new console --framework net6.0
Instalowanie pakietu System.CommandLine
Uruchom następujące polecenie:
dotnet add package System.CommandLine --prerelease
Opcja
--prerelease
jest niezbędna, ponieważ biblioteka jest nadal dostępna w wersji beta.
Zastąp zawartość pliku Program.cs następującym kodem:
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)); } }
Poprzedni kod:
Tworzy opcję o nazwie
--file
typu FileInfo i przypisuje ją do polecenia głównego :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);
Określa, że
ReadFile
jest metodą, która zostanie wywołana po wywołaniu polecenia głównego:rootCommand.SetHandler((file) => { ReadFile(file!); }, fileOption);
Wyświetla zawartość określonego pliku po wywołaniu polecenia głównego:
static void ReadFile(FileInfo file) { File.ReadLines(file.FullName).ToList() .ForEach(line => Console.WriteLine(line)); }
Testowanie aplikacji
Możesz użyć dowolnego z następujących sposobów testowania podczas tworzenia aplikacji wiersza polecenia:
Uruchom polecenie
dotnet build
, a następnie otwórz wiersz polecenia w folderze scl/bin/Debug/net6.0, aby uruchomić plik wykonywalny:dotnet build cd bin/Debug/net6.0 scl --file scl.runtimeconfig.json
Użyj
dotnet run
i przekaż wartości opcji do aplikacji zamiast poleceniarun
, dołączając je po--
, jak w poniższym przykładzie:dotnet run -- --file bin/Debug/net6.0/scl.runtimeconfig.json
Katalog roboczy jest folderem projektu (folderem zawierającym plik csproj), więc ścieżka względna do scl.runtimeconfig.json
pochodzi z folderu projektu.
W wersji zapoznawczej zestawu SDK .NET 7.0.100 można użyć commandLineArgs
pliku launchSettings.json, wykonując polecenie dotnet run --launch-profile <profilename>
.
Opublikuj projekt w folderze, otwórz wiersz polecenia w tym folderze i uruchom plik wykonywalny:
dotnet publish -o publish cd ./publish scl --file scl.runtimeconfig.json
W programie Visual Studio 2022 wybierz pozycję Debuguj>Właściwości debugera z menu, a następnie wprowadź opcje i argumenty w polu argumenty wiersza poleceń. Na przykład:
argumenty wiersza polecenia
Następnie uruchom aplikację, na przykład naciskając Ctrl+F5.
W tym samouczku założono, że używasz pierwszej z tych opcji.
Po uruchomieniu aplikacji wyświetla zawartość pliku określonego przez opcję --file
.
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
}
}
}
Dane wyjściowe pomocy
System.CommandLine
automatycznie wyświetla pomoc:
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
Dane wyjściowe wersji
System.CommandLine
automatycznie udostępnia dane wyjściowe wersji:
scl --version
1.0.0
Dodawanie podpolecenia i opcji
W tej sekcji Ty:
- Utwórz więcej opcji.
- Utwórz podpolecenie.
- Przypisz nowe opcje do nowego podpolecenia.
Nowe opcje umożliwiają skonfigurowanie kolorów tekstu pierwszego planu i tła oraz szybkości odczytu. Te funkcjonalności będą używane do odczytywania kolekcji cytatów, które pochodzą z samouczka aplikacji konsolowej Teleprompter .
Skopiuj plik sampleQuotes.txt z repozytorium GitHub dla tego przykładu do katalogu projektu. Aby uzyskać informacje na temat pobierania plików, zobacz instrukcje w Przykłady i samouczki.
Otwórz plik projektu i dodaj element
<ItemGroup>
tuż przed tagiem zamykającym</Project>
:<ItemGroup> <Content Include="sampleQuotes.txt"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup>
Dodanie tego znacznika powoduje skopiowanie pliku tekstowego do folderu bin/debug/net6.0 podczas kompilowania aplikacji. Dlatego po uruchomieniu pliku wykonywalnego w tym folderze można uzyskać dostęp do pliku według nazwy bez określania ścieżki folderu.
W Program.cspo kodzie tworzącym opcję
--file
utwórz opcje kontrolowania szybkości odczytu i kolorów tekstu: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.");
Po wierszu, który tworzy komendę główną, usuń wiersz, który dodaje opcję
--file
do niej. Usuwasz go tutaj, ponieważ dodasz go do nowego podpolecenia.var rootCommand = new RootCommand("Sample app for System.CommandLine"); //rootCommand.AddOption(fileOption);
Po wierszu, który tworzy główne polecenie, utwórz podpolecenie
read
. Dodaj opcje do tego podpolecenia i dodaj podpolecenia do głównego polecenia.var readCommand = new Command("read", "Read and display the file.") { fileOption, delayOption, fgcolorOption, lightModeOption }; rootCommand.AddCommand(readCommand);
Zastąp kod
SetHandler
następującym kodemSetHandler
dla nowego podpolecenia:readCommand.SetHandler(async (file, delay, fgcolor, lightMode) => { await ReadFile(file!, delay, fgcolor, lightMode); }, fileOption, delayOption, fgcolorOption, lightModeOption);
Nie wywołujesz już
SetHandler
w komendzie głównej, ponieważ komenda główna nie wymaga już obsługi. Gdy polecenie ma podpolecenia, zazwyczaj należy określić jedno z podpolecenia podczas wywoływania aplikacji wiersza polecenia.Zastąp metodę obsługi
ReadFile
następującym kodem: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); }; }
Aplikacja wygląda teraz następująco:
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);
};
}
}
Testowanie nowego podpolecenia
Teraz, jeśli spróbujesz uruchomić aplikację bez określenia podpolecenia, zostanie wyświetlony komunikat o błędzie z komunikatem pomocy określającym dostępne podpolecenia.
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.
Tekst pomocy dla podpolecenia read
pokazuje, że dostępne są cztery opcje. Wyświetla prawidłowe wartości enumeracji.
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
Uruchom polecenie podrzędne read
określając tylko opcję --file
i uzyskasz wartości domyślne pozostałych trzech opcji.
scl read --file sampleQuotes.txt
Domyślne opóźnienie 42 milisekund na znak powoduje spowolnienie szybkości odczytu. Możesz go przyspieszyć, ustawiając --delay
na mniejszą liczbę.
scl read --file sampleQuotes.txt --delay 0
Aby ustawić kolory tekstu, możesz użyć --fgcolor
i --light-mode
:
scl read --file sampleQuotes.txt --fgcolor red --light-mode
Podaj nieprawidłową wartość dla --delay
i zostanie wyświetlony komunikat o błędzie:
scl read --file sampleQuotes.txt --delay forty-two
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.
Podaj nieprawidłową wartość dla --file
i otrzymasz wyjątek:
scl read --file nofile
Unhandled exception: System.IO.FileNotFoundException:
Could not find file 'C:\bin\Debug\net6.0\nofile'.
Dodawanie podpoleceń i walidacji niestandardowych
W tej sekcji zostanie utworzona ostateczna wersja aplikacji. Po zakończeniu aplikacja będzie mieć następujące polecenia i opcje:
- polecenie główne z opcją global* o nazwie
--file
- polecenie
quotes
-
read
polecenie z opcjami o nazwie--delay
,--fgcolor
i--light-mode
-
add
polecenie z argumentami o nazwachquote
ibyline
-
delete
komenda z opcją o nazwie--search-terms
-
- polecenie
* Opcja globalna jest dostępna dla polecenia, do którego jest przypisana, oraz rekurencyjnie dla wszystkich jego podpoleceń.
Oto przykładowe dane wejściowe wiersza polecenia, które wywołują każde z dostępnych poleceń z jego opcjami i argumentami:
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"
W Program.cszastąp kod, który tworzy opcję
--file
następującym kodem: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); } });
Ten kod używa ParseArgument<T> w celu zapewnienia niestandardowej analizy, walidacji i obsługi błędów.
Bez tego kodu brakujące pliki są raportowane przy pomocy wyjątków i śladu stosu. Po wyświetleniu tego kodu zostanie wyświetlony tylko określony komunikat o błędzie.
Ten kod określa również wartość domyślną, dlatego ustawia
isDefault
natrue
. Jeśli nie ustawiszisDefault
natrue
, delegatparseArgument
nie zostanie wywołany, gdy nie podano żadnych danych wejściowych dla--file
.Po kodzie tworzącym
lightModeOption
dodaj opcje i argumenty dla poleceńadd
idelete
: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.");
Ustawienie AllowMultipleArgumentsPerToken pozwala pominąć nazwę opcji
--search-terms
podczas określania elementów na liście po pierwszym. Przykłady danych wejściowych wiersza polecenia, które są równoważne, to:scl quotes delete --search-terms David "You can do" scl quotes delete --search-terms David --search-terms "You can do"
Zastąp kod, który tworzy polecenie główne i polecenie
read
następującym kodem: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);
Ten kod wprowadza następujące zmiany:
Usuwa opcję
--file
z poleceniaread
.Dodaje opcję
--file
jako opcję globalną do polecenia głównego.Tworzy polecenie
quotes
i dodaje je do głównego polecenia.Dodaje polecenie
read
do poleceniaquotes
zamiast do polecenia głównego.Tworzy polecenia
add
idelete
i dodaje je do poleceniaquotes
.
Wynikiem jest następująca hierarchia poleceń:
- Polecenie główne
quotes
read
add
delete
Aplikacja implementuje teraz zalecany wzorzec, w którym polecenie nadrzędne (
quotes
) określa obszar lub grupę, a jego polecenia podrzędne (read
,add
,delete
) są akcjami.Opcje globalne są stosowane do polecenia i rekursywnie do podpoleceń. Ponieważ
--file
jest w poleceniu głównym, będzie on automatycznie dostępny we wszystkich podpoleceniach aplikacji.Po kodzie
SetHandler
dodaj nowy kodSetHandler
dla nowych poleceń podrzędnych:deleteCommand.SetHandler((file, searchTerms) => { DeleteFromFile(file!, searchTerms); }, fileOption, searchTermsOption); addCommand.SetHandler((file, quote, byline) => { AddToFile(file!, quote, byline); }, fileOption, quoteArgument, bylineArgument);
Podkomenda
quotes
nie ma procedury obsługi, ponieważ nie jest komendą końcową. Podpoleceniaread
,add
idelete
są poleceniami końcowymi wquotes
, aSetHandler
jest wywoływane dla każdego z nich.Dodaj programy obsługi dla
add
idelete
.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(); }
Zakończona aplikacja wygląda następująco:
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();
}
}
Skompiluj projekt, a następnie spróbuj wykonać następujące polecenia.
Prześlij nieistniejący plik do --file
przy użyciu polecenia read
, a zamiast wyjątku i śledzenia stosu otrzymasz komunikat o błędzie:
scl quotes read --file nofile
File does not exist
Spróbuj uruchomić polecenie podrzędne quotes
i zostanie wyświetlony komunikat kierujący cię do używania read
, add
lub 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.
Uruchom polecenie podrzędne add
, a następnie przyjrzyj się końcu pliku tekstowego, aby zobaczyć dodany tekst:
scl quotes add "Hello world!" "Nancy Davolio"
Uruchom polecenie podrzędne delete
z ciągami wyszukiwania od początku pliku, a następnie przyjrzyj się początku pliku tekstowego, aby zobaczyć, gdzie został usunięty tekst:
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
Uwaga
Jeśli korzystasz z folderu bin/debug/net6.0, w tym folderze znajdziesz plik ze zmianami z poleceń add
i delete
. Kopia pliku w folderze projektu pozostaje niezmieniona.
Następne kroki
Podczas tego samouczka stworzyłeś prostą aplikację wiersza polecenia, która korzysta z System.CommandLine
. Aby dowiedzieć się więcej o bibliotece, zobacz System.CommandLine omówienie.