Zelfstudie: Aan de slag met System.CommandLine
Belangrijk
System.CommandLine
bevindt zich momenteel in PREVIEW en deze documentatie is voor versie 2.0 beta 4.
Sommige informatie heeft betrekking op prerelease van een product dat aanzienlijk kan worden gewijzigd voordat het wordt uitgebracht. Microsoft biedt geen enkele expliciete of impliciete garanties met betrekking tot de informatie die hier wordt verstrekt.
In deze zelfstudie ziet u hoe u een .NET-opdrachtregel-app maakt die gebruikmaakt van de System.CommandLine
bibliotheek. U begint met het maken van een eenvoudige hoofdopdracht met één optie. Vervolgens voegt u aan die basis toe en maakt u een complexere app die meerdere subopdrachten en verschillende opties voor elke opdracht bevat.
In deze zelfstudie leert u het volgende:
- Opdrachten, opties en argumenten maken.
- Geef standaardwaarden op voor opties.
- Wijs opties en argumenten toe aan opdrachten.
- Wijs een optie recursief toe aan alle subopdrachten onder een opdracht.
- Werken met meerdere niveaus van geneste subopdrachten.
- Maak aliassen voor opdrachten en opties.
- Werken met
string
de optietypen ,string[]
int
,bool
,FileInfo
en opsomming. - Optiewaarden binden aan opdrachthandlercode.
- Gebruik aangepaste code voor het parseren en valideren van opties.
Vereisten
- Een code-editor, zoals Visual Studio Code met de C#-extensie.
- De .NET 6 SDK.
of
- Visual Studio 2022 met de workload .NET Desktop Development geïnstalleerd.
De app maken
Maak een .NET 6-console-app-project met de naam 'scl'.
Maak een map met de naam scl voor het project en open vervolgens een opdrachtprompt in de nieuwe map.
Voer de volgende opdracht uit:
dotnet new console --framework net6.0
Installeer het pakket System.CommandLine
Voer de volgende opdracht uit:
dotnet add package System.CommandLine --prerelease
De
--prerelease
optie is nodig omdat de bibliotheek nog in bèta is.
Vervang de inhoud van Program.cs door de volgende code:
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)); } }
Met de voorgaande code wordt:
Hiermee maakt u een optie met de naam
--file
van het type FileInfo en wijst deze toe aan de hoofdopdracht: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);
Hiermee geeft u op dat
ReadFile
de methode is die wordt aangeroepen wanneer de hoofdopdracht wordt aangeroepen:rootCommand.SetHandler((file) => { ReadFile(file!); }, fileOption);
Geeft de inhoud van het opgegeven bestand weer wanneer de hoofdopdracht wordt aangeroepen:
static void ReadFile(FileInfo file) { File.ReadLines(file.FullName).ToList() .ForEach(line => Console.WriteLine(line)); }
De app testen
U kunt een van de volgende manieren gebruiken om te testen tijdens het ontwikkelen van een opdrachtregel-app:
Voer de
dotnet build
opdracht uit en open vervolgens een opdrachtprompt in de map scl/bin/Debug/net6.0 om het uitvoerbare bestand uit te voeren:dotnet build cd bin/Debug/net6.0 scl --file scl.runtimeconfig.json
Gebruik
dotnet run
en geef optiewaarden door aan de app in plaats van aan derun
opdracht door ze op te slaan na--
, zoals in het volgende voorbeeld:dotnet run -- --file scl.runtimeconfig.json
In .NET 7.0.100 SDK Preview kunt u het
commandLineArgs
bestand launchSettings.json gebruiken door de opdrachtdotnet run --launch-profile <profilename>
uit te voeren.Publiceer het project naar een map, open een opdrachtprompt naar die map en voer het uitvoerbare bestand uit:
dotnet publish -o publish cd ./publish scl --file scl.runtimeconfig.json
Selecteer in Visual Studio 2022 Debug Debug>Properties in het menu en voer de opties en argumenten in het vak Opdrachtregelargumenten in. Bijvoorbeeld:
Voer vervolgens de app uit, bijvoorbeeld door op Ctrl+F5 te drukken.
In deze zelfstudie wordt ervan uitgegaan dat u de eerste van deze opties gebruikt.
Wanneer u de app uitvoert, wordt de inhoud van het bestand weergegeven dat is opgegeven met de --file
optie.
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
}
}
}
Help-uitvoer
System.CommandLine
biedt automatisch help-uitvoer:
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
Versie-uitvoer
System.CommandLine
biedt automatisch versie-uitvoer:
scl --version
1.0.0
Een subopdracht en opties toevoegen
In deze sectie doet u het volgende:
- Maak meer opties.
- Maak een subopdracht.
- Wijs de nieuwe opties toe aan de nieuwe subopdracht.
Met de nieuwe opties kunt u de voorgrond- en achtergrondtekstkleuren en de leessnelheid configureren. Deze functies worden gebruikt om een verzameling aanhalingstekens te lezen die afkomstig zijn uit de zelfstudie teleprompter-console-app.
Kopieer het sampleQuotes.txt-bestand uit de GitHub-opslagplaats voor dit voorbeeld naar uw projectmap. Zie de instructies in Voorbeelden en zelfstudies voor meer informatie over het downloaden van bestanden.
Open het projectbestand en voeg een
<ItemGroup>
element toe vlak voor de afsluitende</Project>
tag:<ItemGroup> <Content Include="sampleQuotes.txt"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup>
Als u deze markering toevoegt, wordt het tekstbestand gekopieerd naar de map bin/debug/net6.0 wanneer u de app bouwt. Dus wanneer u het uitvoerbare bestand in die map uitvoert, hebt u toegang tot het bestand op naam zonder een mappad op te geven.
Maak in Program.cs, na de code waarmee de
--file
optie wordt gemaakt, opties om de leessnelheid en tekstkleuren te bepalen: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.");
Verwijder na de regel waarmee de hoofdopdracht wordt gemaakt de regel waarmee de
--file
optie wordt toegevoegd. U verwijdert deze hier omdat u deze toevoegt aan een nieuwe subopdracht.var rootCommand = new RootCommand("Sample app for System.CommandLine"); //rootCommand.AddOption(fileOption);
Maak een
read
subopdracht na de regel waarmee de hoofdopdracht wordt gemaakt. Voeg de opties toe aan deze subopdracht en voeg de subopdracht toe aan de hoofdopdracht.var readCommand = new Command("read", "Read and display the file.") { fileOption, delayOption, fgcolorOption, lightModeOption }; rootCommand.AddCommand(readCommand);
Vervang de
SetHandler
code door de volgendeSetHandler
code voor de nieuwe subopdracht:readCommand.SetHandler(async (file, delay, fgcolor, lightMode) => { await ReadFile(file!, delay, fgcolor, lightMode); }, fileOption, delayOption, fgcolorOption, lightModeOption);
U roept
SetHandler
de hoofdopdracht niet meer aan omdat de hoofdopdracht geen handler meer nodig heeft. Wanneer een opdracht subopdrachten bevat, moet u doorgaans een van de subopdrachten opgeven bij het aanroepen van een opdrachtregel-app.Vervang de
ReadFile
handlermethode door de volgende code: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); }; }
De app ziet er nu als volgt uit:
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);
};
}
}
De nieuwe subopdracht testen
Als u de app nu probeert uit te voeren zonder de subopdracht op te geven, krijgt u een foutbericht, gevolgd door een Help-bericht met de subopdracht die beschikbaar is.
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.
In de Help-tekst voor de subopdracht ziet u read
dat er vier opties beschikbaar zijn. Er worden geldige waarden voor de enum weergegeven.
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
Voer de subopdracht read
uit en geef alleen de --file
optie op en u krijgt de standaardwaarden voor de andere drie opties.
scl read --file sampleQuotes.txt
De standaardvertraging van 42 milliseconden per teken zorgt voor een trage leessnelheid. U kunt dit versnellen door in te stellen --delay
op een lager getal.
scl read --file sampleQuotes.txt --delay 0
U kunt en --light-mode
gebruiken --fgcolor
om tekstkleuren in te stellen:
scl read --file sampleQuotes.txt --fgcolor red --light-mode
Geef een ongeldige waarde op voor --delay
en u krijgt een foutbericht:
scl read --file sampleQuotes.txt --delay forty-two
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.
Geef een ongeldige waarde op voor --file
en u krijgt een uitzondering:
scl read --file nofile
Unhandled exception: System.IO.FileNotFoundException:
Could not find file 'C:\bin\Debug\net6.0\nofile'.
Subopdrachten en aangepaste validatie toevoegen
In deze sectie wordt de definitieve versie van de app gemaakt. Wanneer u klaar bent, heeft de app de volgende opdrachten en opties:
- hoofdopdracht met een globale* optie met de naam
--file
- De opdracht
quotes
read
opdracht met opties met de naam--delay
,--fgcolor
en--light-mode
add
opdracht met argumenten met de naamquote
enbyline
delete
opdracht met optie met naam--search-terms
- De opdracht
* Er is een globale optie beschikbaar voor de opdracht waaraan deze is toegewezen en recursief aan alle subopdrachten.
Hier volgt een voorbeeld van opdrachtregelinvoer die elk van de beschikbare opdrachten aanroept met de bijbehorende opties en argumenten:
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"
Vervang in Program.cs de code waarmee de
--file
optie wordt gemaakt door de volgende code: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); } });
Deze code gebruikt ParseArgument<T> om aangepaste parsering, validatie en foutafhandeling te bieden.
Zonder deze code worden ontbrekende bestanden gerapporteerd met een uitzondering en stack-tracering. Met deze code wordt alleen het opgegeven foutbericht weergegeven.
Met deze code wordt ook een standaardwaarde opgegeven. Daarom wordt ingesteld
isDefault
optrue
. Als u niet insteltisDefault
optrue
, wordt deparseArgument
gemachtigde niet aangeroepen wanneer er geen invoer is opgegeven voor--file
.Voeg na de code waarmee wordt gemaakt
lightModeOption
opties en argumenten toe voor deadd
opdrachten endelete
: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.");
Met AllowMultipleArgumentsPerToken de instelling kunt u de naam van de
--search-terms
optie weglaten bij het opgeven van elementen in de lijst na de eerste. De volgende voorbeelden van opdrachtregelinvoer zijn equivalent:scl quotes delete --search-terms David "You can do" scl quotes delete --search-terms David --search-terms "You can do"
Vervang de code waarmee de hoofdopdracht wordt gemaakt en de
read
opdracht door de volgende code: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);
Met deze code worden de volgende wijzigingen aangebracht:
Hiermee verwijdert u de
--file
optie uit deread
opdracht.Hiermee voegt u de
--file
optie toe als een globale optie aan de hoofdopdracht.Hiermee maakt u een
quotes
opdracht en voegt u deze toe aan de hoofdopdracht.Hiermee voegt u de
read
opdracht toe aan dequotes
opdracht in plaats van aan de hoofdopdracht.Hiermee maakt
add
u endelete
opdrachten en voegt u deze toe aan dequotes
opdracht.
Het resultaat is de volgende opdrachthiërarchie:
- Hoofdopdracht
quotes
read
add
delete
De app implementeert nu het aanbevolen patroon waarbij de bovenliggende opdracht (
quotes
) een gebied of groep opgeeft en de onderliggende opdrachten (read
,add
,delete
) acties zijn.Globale opties worden toegepast op de opdracht en recursief op subopdrachten. Omdat
--file
zich in de hoofdopdracht bevindt, is deze automatisch beschikbaar in alle subopdrachten van de app.Voeg na de
SetHandler
code nieuweSetHandler
code toe voor de nieuwe subopdrachten:deleteCommand.SetHandler((file, searchTerms) => { DeleteFromFile(file!, searchTerms); }, fileOption, searchTermsOption); addCommand.SetHandler((file, quote, byline) => { AddToFile(file!, quote, byline); }, fileOption, quoteArgument, bylineArgument);
Subopdracht
quotes
heeft geen handler omdat het geen leaf-opdracht is. Subopdrachtenread
,add
endelete
zijn leaf-opdrachten onderquotes
enSetHandler
worden voor elk van deze opdrachten aangeroepen.Voeg de handlers voor
add
endelete
toe.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(); }
De voltooide app ziet er als volgt uit:
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();
}
}
Bouw het project en voer de volgende opdrachten uit.
Verzend een niet-bestaand bestand naar --file
met de read
opdracht en u krijgt een foutbericht in plaats van een uitzonderings- en stacktracering:
scl quotes read --file nofile
File does not exist
Probeer een subopdracht quotes
uit te voeren en u krijgt een bericht met de opdracht om , add
of delete
te gebruikenread
:
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.
Voer subopdracht add
uit en bekijk het einde van het tekstbestand om de toegevoegde tekst te zien:
scl quotes add "Hello world!" "Nancy Davolio"
Voer subopdracht delete
uit met zoektekenreeksen vanaf het begin van het bestand en kijk vervolgens naar het begin van het tekstbestand om te zien waar tekst is verwijderd:
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
Notitie
Als u in de map bin/debug/net6.0 werkt, vindt u in die map het bestand met wijzigingen in de add
opdrachten en delete
. De kopie van het bestand in de projectmap blijft ongewijzigd.
Volgende stappen
In deze zelfstudie hebt u een eenvoudige opdrachtregel-app gemaakt die gebruikmaakt van System.CommandLine
. Zie Overzicht voor meer informatie over de bibliotheekSystem.CommandLine.