βΉοΈ Skipped - page is already crawled
| Filter | Status | Condition | Details |
|---|---|---|---|
| HTTP status | PASS | download_http_code = 200 | HTTP 200 |
| Age cutoff | PASS | download_stamp > now() - 6 MONTH | 0.3 months ago (distributed domain, exempt) |
| History drop | PASS | isNull(history_drop_reason) | No drop reason |
| Spam/ban | PASS | fh_dont_index != 1 AND ml_spam_score = 0 | ml_spam_score=0 |
| Canonical | PASS | meta_canonical IS NULL OR = '' OR = src_unparsed | Not set |
| Property | Value |
|---|---|
| URL | https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial |
| Last Crawled | 2026-04-02 14:46:38 (10 days ago) |
| First Indexed | 2022-09-25 06:28:25 (3 years ago) |
| HTTP Status Code | 200 |
| Meta Title | Tutorial: Get started with System.CommandLine - .NET | Microsoft Learn |
| Meta Description | Learn how to use the System.CommandLine library for command-line apps. |
| Meta Canonical | null |
| Boilerpipe Text | This tutorial shows how to create a .NET command-line app that uses the
System.CommandLine
library
. You'll begin by creating a simple root command that has one option. Then you'll build on that base, creating a more complex app that contains multiple subcommands and different options for each command.
In this tutorial, you learn how to:
Create commands, options, and arguments.
Specify default values for options.
Assign options and arguments to commands.
Assign an option recursively to all subcommands under a command.
Work with multiple levels of nested subcommands.
Create aliases for commands and options.
Work with
string
,
string[]
,
int
,
bool
,
FileInfo
, and enum option types.
Read option values in command action code.
Use custom code for parsing and validating options.
The latest
.NET SDK
Visual Studio Code
editor
The
C# DevKit
Or
Visual Studio
with the
.NET desktop development
workload installed.
Create a .NET 9 console app project named "scl".
Create a folder named
scl
for the project, and then open a command prompt in the new folder.
Run the following command:
dotnet
new
console
--framework
net9.
0
Run the following command:
dotnet
add
package System.CommandLine
Or, in .NET 10+:
dotnet
package
add
System.CommandLine
Replace the contents of
Program.cs
with the following code:
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);
}
}
}
The preceding code:
Creates an
option
named
--file
of type
FileInfo
and adds it to the
root command
:
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);
Parses
args
and checks whether any value was provided for the
--file
option. If so, it calls the
ReadFile
method using the parsed value and returns exit code
0
:
ParseResult parseResult = rootCommand.Parse(args);
if
(parseResult.Errors.Count ==
0
&& parseResult.GetValue(fileOption)
is
FileInfo parsedFile)
{
ReadFile(parsedFile);
return
0
;
}
If no value was provided for
--file
, it prints available parse errors and returns exit code
1
:
foreach
(ParseError parseError
in
parseResult.Errors)
{
Console.Error.WriteLine(parseError.Message);
}
return
1
;
The
ReadFile
method reads the specified file and displays its contents on the console:
static
void
ReadFile
(
FileInfo file
)
{
foreach
(
string
line
in
File.ReadLines(file.FullName))
{
Console.WriteLine(line);
}
}
You can use any of the following ways to test while developing a command-line app:
Run the
dotnet build
command, and then open a command prompt in the build output folder to run the executable:
dotnet build
cd bin/Debug/net9.0
scl --file scl.runtimeconfig.json
Use
dotnet run
and pass option values to the app instead of to the
run
command by including them after
--
, as in the following example:
dotnet
run
--
--file
bin/Debug/net9.
0
/scl.runtimeconfig.json
(This tutorial assumes you're using the first of these options.)
When you run the app, it displays the contents of the file specified by the
--file
option.
{
"runtimeOptions": {
"tfm": "net9.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
}
}
}
But if you ask it to display the help by providing
--help
, nothing gets printed to the console. That's because the app doesn't yet handle the scenario where
--file
isn't provided and there are no parse errors.
System.CommandLine allows you to specify an action that's invoked when a given symbol (command, directive, or option) is parsed successfully. The action is a delegate that takes a
ParseResult
parameter and returns an
int
exit code. (
Async actions
are also available). The exit code is returned by the
ParseResult.Invoke(InvocationConfiguration)
method and can be used to indicate whether the command was executed successfully or not.
Replace the contents of
Program.cs
with the following code:
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);
}
}
}
The preceding code:
Specifies that
ReadFile
is the method that will be called when the root command is
invoked
:
rootCommand.SetAction(parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
return
0
;
});
Parses
args
and
invokes
the result:
ParseResult parseResult = rootCommand.Parse(args);
return
parseResult.Invoke();
When you run the app, it displays the contents of the file specified by the
--file
option.
If you ask it to display the help by specifying
scl --help
, the following output gets printed:
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 console
RootCommand
by default provides
Help option
,
Version option
, and
Suggest directive
. The
ParseResult.Invoke(InvocationConfiguration)
method is responsible for invoking the action of the parsed symbol. It could be the action explicitly defined for the command or the help action defined by
System.CommandLine
for
System.CommandLine.Help.HelpOption
.
Moreover, when the
Invoke
method detects any parse errors, it prints them to the standard error, prints help to standard output, and returns
1
as the exit code:
scl --invalid bla
Unrecognized command or argument '--invalid'.
Unrecognized command or argument 'bla'.
In this section, you:
Create more options.
Create a subcommand.
Assign the new options to the new subcommand.
The new options let the end user configure the foreground and background text colors and the readout speed. These features will be used to read a collection of quotes that comes from the
Teleprompter console app tutorial
.
Copy the
sampleQuotes.txt
file from the dotnet samples repository into your project directory.
(For information on how to download files, see the instructions in
Samples and tutorials
.)
Open the project file and add an
<ItemGroup>
element just before the closing
</Project>
tag:
<
ItemGroup
>
<
Content
Include
=
"sampleQuotes.txt"
>
<
CopyToOutputDirectory
>
Always
</
CopyToOutputDirectory
>
</
Content
>
</
ItemGroup
>
Adding this markup causes the text file to be copied to the output folder when you build the app. So when you run the executable in that folder, you can access the file by name without specifying a folder path.
In
Program.cs
, after the code that creates the
--file
option, create options to control the readout speed and text colors:
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"
};
After the line that creates the root command, delete the code that adds the
--file
option to it. You're removing it here because you'll add it to a new subcommand.
After the line that creates the root command, create a
read
subcommand. Add the options to this subcommand (by using collection initializer syntax rather than
Options
property), and add the subcommand to the root command.
Command readCommand =
new
(
"read"
,
"Read and display the file."
)
{
fileOption,
delayOption,
fgcolorOption,
lightModeOption
};
rootCommand.Subcommands.Add(readCommand);
Replace the
SetAction
code with the following
SetAction
code for the new subcommand:
readCommand.SetAction(parseResult => ReadFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(delayOption),
parseResult.GetValue(fgcolorOption),
parseResult.GetValue(lightModeOption)));
You're no longer calling
SetAction
on the root command because the root command no longer needs an action. When a command has subcommands, you typically have to specify one of the subcommands when invoking a command-line app.
Replace the
ReadFile
action method with the following code:
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));
}
}
The app now looks like this:
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));
}
}
}
Now if you try to run the app without specifying the subcommand, you get an error message followed by a help message that specifies the subcommand that is available.
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.
The help text for subcommand
read
shows that four options are available. It shows valid values for the enum.
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
Run subcommand
read
specifying only the
--file
option, and you get the default values for the other three options.
scl read --file sampleQuotes.txt
The 42 milliseconds per character default delay causes a slow readout speed. You can speed it up by setting
--delay
to a lower number.
scl read --file sampleQuotes.txt --delay 0
You can use
--fgcolor
and
--light-mode
to set text colors:
scl read --file sampleQuotes.txt --fgcolor red --light-mode
If you provide an invalid value for
--delay
, you get an error message:
scl read --file sampleQuotes.txt --delay forty-two
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.
If you provide an invalid value for
--file
, you get an exception:
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''
This section creates the final version of the app. When finished, the app will have the following commands and options:
Command
Options
Arguments
Root command
--file
(
recursive
)
quotes
read
--delay
,
--fgcolor
,
--light-mode
add
quote
and
byline
delete
--search-terms
(A
recursive
option is available to the command it's assigned to and recursively to all its subcommands.)
Here's sample command-line input that invokes each of the available commands with its options and arguments:
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"
In
Program.cs
, replace the code that creates the
--file
option with the following code:
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);
}
}
};
This code uses
ArgumentResult
to provide custom parsing, validation, and error handling.
Without this code, missing files are reported with an exception and stack trace. With this code just the specified error message is displayed.
This code also specifies a default value, which is why it sets
Option<T>.DefaultValueFactory
to custom parsing method.
After the code that creates
lightModeOption
, add options and arguments for the
add
and
delete
commands:
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."
};
The
AllowMultipleArgumentsPerToken
setting lets you omit the
--search-terms
option name when specifying elements in the list after the first one. It makes the following examples of command-line input equivalent:
scl quotes delete --search-terms David "You can do"
scl quotes delete --search-terms David --search-terms "You can do"
Replace the code that creates the root command and the
read
command with the following code:
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);
This code makes the following changes:
Removes the
--file
option from the
read
command.
Adds the
--file
option as a recursive option to the root command.
Creates a
quotes
command and adds it to the root command.
Adds the
read
command to the
quotes
command instead of to the root command.
Creates
add
and
delete
commands and adds them to the
quotes
command.
The result is the following command hierarchy:
Root command
βββ
quotes
βββ
read
βββ
add
βββ
delete
The app now implements the
recommended
pattern where the parent command (
quotes
) specifies an area or group, and its children commands (
read
,
add
,
delete
) are actions.
Recursive options are applied to the command and recursively to subcommands. Since
--file
is on the root command, it will be available automatically in all subcommands of the app.
After the
SetAction
code, add new
SetAction
code for the new subcommands:
deleteCommand.SetAction(parseResult => DeleteFromFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(searchTermsOption)));
addCommand.SetAction(parseResult => AddToFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(quoteArgument),
parseResult.GetValue(bylineArgument))
);
Subcommand
quotes
doesn't have an action because it isn't a leaf command. Subcommands
read
,
add
, and
delete
are leaf commands under
quotes
, and
SetAction
is called for each of them.
Add the actions for
add
and
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}
"
);
}
The finished app looks like this:
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}
"
);
}
}
Build the project, and then try the following commands.
Submit a nonexistent file to
--file
with the
read
command, and you get an error message instead of an exception and stack trace:
scl quotes read --file nofile
File does not exist
If you try to run subcommand
quotes
, you get a message directing you to use
read
,
add
, or
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.
Run subcommand
add
, and then look at the end of the text file to see the added text:
scl quotes add "Hello world!" "Nancy Davolio"
Run subcommand
delete
with search strings from the beginning of the file, and then look at the beginning of the text file to see where text was removed:
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
Note
The file in the output folder reflects the changes from the
add
and
delete
commands. The copy of the file in the project folder remains unchanged.
In this tutorial, you created a simple command-line app that uses
System.CommandLine
. To learn more about the library, see
System.CommandLine overview
. To enable tab completion capabilities, see
Tab completion for System.CommandLine
. |
| Markdown | [Skip to main content](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#main) [Skip to Ask Learn chat experience](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial)
## Microsoft Build 2026
June 2-3, 2026
Go deep on real code and real systems in San Francisco and online
[Learn more](https://aka.ms/MSBuild_FY26_BN_MSLearn_Hero)
Dismiss alert
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
[Download Microsoft Edge](https://go.microsoft.com/fwlink/p/?LinkID=2092881%20) [More info about Internet Explorer and Microsoft Edge](https://learn.microsoft.com/en-us/lifecycle/faq/internet-explorer-microsoft-edge)
[Learn](https://learn.microsoft.com/en-us/)
[Sign in](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial)
![]()
![]()
- [Profile](https://learn.microsoft.com/en-us/users/me/activity/)
- [Settings](https://learn.microsoft.com/en-us/users/me/settings/)
[Sign out](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial)
[Learn](https://learn.microsoft.com/en-us/)
- Documentation
- [All product documentation](https://learn.microsoft.com/en-us/docs/)
- [Azure documentation](https://learn.microsoft.com/en-us/azure/?product=popular)
- [Dynamics 365 documentation](https://learn.microsoft.com/en-us/dynamics365/)
- [Microsoft Copilot documentation](https://learn.microsoft.com/en-us/copilot/)
- [Microsoft 365 documentation](https://learn.microsoft.com/en-us/microsoft-365/)
- [Power Platform documentation](https://learn.microsoft.com/en-us/power-platform/)
- [Code samples](https://learn.microsoft.com/en-us/samples/)
- [Troubleshooting documentation](https://learn.microsoft.com/en-us/troubleshoot/)
Free to join. Request to attend.
[Microsoft AI Tour](https://aitour.microsoft.com/?wt.mc_id=itour26_learnmarketingspot_wwl)
Take your business to the AI frontier.
- Training & Labs
- [All training](https://learn.microsoft.com/en-us/training/)
- [Azure training](https://learn.microsoft.com/en-us/training/browse/?products=azure)
- [Dynamics 365 training](https://learn.microsoft.com/en-us/training/browse/?products=dynamics-365)
- [Microsoft Copilot training](https://learn.microsoft.com/en-us/training/browse/?products=ms-copilot)
- [Microsoft 365 training](https://learn.microsoft.com/en-us/training/browse/?products=m365)
- [Microsoft Power Platform training](https://learn.microsoft.com/en-us/training/browse/?products=power-platform)
- [Labs](https://learn.microsoft.com/en-us/labs/)
- [Credentials](https://learn.microsoft.com/en-us/credentials/)
- [Career paths](https://learn.microsoft.com/en-us/training/career-paths/)
Free to join. Request to attend.
[Microsoft AI Tour](https://aitour.microsoft.com/?wt.mc_id=itour26_learnmarketingspot_wwl)
Take your business to the AI frontier.
- Q\&A
- [Ask a question](https://learn.microsoft.com/en-us/answers/questions/ask/)
- [Azure questions](https://learn.microsoft.com/en-us/answers/tags/133/azure/)
- [Windows questions](https://learn.microsoft.com/en-us/answers/tags/60/windows/)
- [Microsoft 365 questions](https://learn.microsoft.com/en-us/answers/tags/9/m365/)
- [Microsoft Outlook questions](https://learn.microsoft.com/en-us/answers/tags/131/office-outlook/)
- [Microsoft Teams questions](https://learn.microsoft.com/en-us/answers/tags/108/office-teams/)
- [Popular tags](https://learn.microsoft.com/en-us/answers/tags/)
- [All questions](https://learn.microsoft.com/en-us/answers/questions/)
Free to join. Request to attend.
[Microsoft AI Tour](https://aitour.microsoft.com/?wt.mc_id=itour26_learnmarketingspot_wwl)
Take your business to the AI frontier.
- Topics
- [Artificial intelligence](https://learn.microsoft.com/en-us/ai/)
Learning hub to build AI skills
- [Compliance](https://learn.microsoft.com/en-us/compliance/)
Compliance resources you need to get started with your business
- [DevOps](https://learn.microsoft.com/en-us/devops/)
DevOps practices, Git version control and Agile methods
- [Learn for Organizations](https://learn.microsoft.com/en-us/training/organizations/)
Curated offerings from Microsoft to boost your teamβs technical skills
- [Platform engineering](https://learn.microsoft.com/en-us/platform-engineering/)
Tools from Microsoft and others to build personalized developer experiences
- [Security](https://learn.microsoft.com/en-us/security/)
Guidance to help you tackle security challenges
- [Assessments](https://learn.microsoft.com/en-us/assessments/)
Interactive guidance with custom recommendations
- [Student hub](https://learn.microsoft.com/en-us/training/student-hub/)
Self-paced and interactive training for students
- [Educator center](https://learn.microsoft.com/en-us/training/educator-center/)
Resources for educators to bring technical innovation in their classroom
Free to join. Request to attend.
[Microsoft AI Tour](https://aitour.microsoft.com/?wt.mc_id=itour26_learnmarketingspot_wwl)
Take your business to the AI frontier.
[Sign in](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial)
![]()
![]()
- [Profile](https://learn.microsoft.com/en-us/users/me/activity/)
- [Settings](https://learn.microsoft.com/en-us/users/me/settings/)
[Sign out](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial)
[.NET](https://learn.microsoft.com/en-us/dotnet/)
- Languages
- [C\#](https://learn.microsoft.com/en-us/dotnet/csharp/)
- [F\#](https://learn.microsoft.com/en-us/dotnet/fsharp/)
- [Visual Basic](https://learn.microsoft.com/en-us/dotnet/visual-basic/)
- Features
- [Fundamentals](https://learn.microsoft.com/en-us/dotnet/fundamentals/)
- [Tools and diagnostics](https://learn.microsoft.com/en-us/dotnet/navigate/tools-diagnostics/)
- AI
- [Generative AI](https://learn.microsoft.com/en-us/dotnet/ai/)
- [ML.NET](https://learn.microsoft.com/en-us/dotnet/machine-learning/)
- [Migrate from .NET Framework](https://learn.microsoft.com/en-us/dotnet/navigate/migration-guide/)
- [Compatibility](https://learn.microsoft.com/en-us/dotnet/core/compatibility/breaking-changes/)
- [Advanced programming](https://learn.microsoft.com/en-us/dotnet/navigate/advanced-programming/)
- [DevOps and testing](https://learn.microsoft.com/en-us/dotnet/navigate/devops-testing/)
- [Security](https://learn.microsoft.com/en-us/dotnet/navigate/security/)
- [Data access](https://learn.microsoft.com/en-us/dotnet/navigate/data-access/)
- Workloads
- [Web](https://learn.microsoft.com/en-us/aspnet/core/)
- [Cloud](https://learn.microsoft.com/en-us/dotnet/azure/)
- Cloud-native
- [Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/)
- [Microsoft Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/)
- Desktop and mobile
- [.NET Multi-platform App UI (.NET MAUI)](https://learn.microsoft.com/en-us/dotnet/maui/)
- [Windows Presentation Foundation](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/)
- [Windows Forms](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/)
- [WinUI apps](https://learn.microsoft.com/en-us/windows/apps/winui/)
- APIs
- [.NET](https://learn.microsoft.com/en-us/dotnet/api/?view=net-10.0)
- [Aspire](https://learn.microsoft.com/en-us/dotnet/api/?view=dotnet-aspire-9.0)
- [.NET Framework](https://learn.microsoft.com/en-us/dotnet/api/?view=netframework-4.8&preserve-view=true)
- [.NET Multi-platform App UI (.NET MAUI)](https://learn.microsoft.com/en-us/dotnet/api/?view=net-maui-9.0)
- [ASP.NET](https://learn.microsoft.com/en-us/dotnet/api/?view=aspnetcore-10.0)
- [ML.NET](https://learn.microsoft.com/en-us/dotnet/api/?view=ml-dotnet&preserve-view=true)
- [Troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/developer/dotnet/welcome-dotnet-landing)
- Resources
- [Library guidance](https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/)
- [What is .NET?](https://dotnet.microsoft.com/learn/dotnet/what-is-dotnet)
- [.NET architecture guides](https://dotnet.microsoft.com/learn/dotnet/architecture-guides)
- [Learning materials](https://dotnet.microsoft.com/learn)
- [Downloads](https://dotnet.microsoft.com/download)
- [Community](https://dotnet.microsoft.com/platform/community)
- [Support](https://dotnet.microsoft.com/platform/support)
- [Blog](https://devblogs.microsoft.com/dotnet/)
- More
- Languages
- [C\#](https://learn.microsoft.com/en-us/dotnet/csharp/)
- [F\#](https://learn.microsoft.com/en-us/dotnet/fsharp/)
- [Visual Basic](https://learn.microsoft.com/en-us/dotnet/visual-basic/)
- Features
- [Fundamentals](https://learn.microsoft.com/en-us/dotnet/fundamentals/)
- [Tools and diagnostics](https://learn.microsoft.com/en-us/dotnet/navigate/tools-diagnostics/)
- AI
- [Generative AI](https://learn.microsoft.com/en-us/dotnet/ai/)
- [ML.NET](https://learn.microsoft.com/en-us/dotnet/machine-learning/)
- [Migrate from .NET Framework](https://learn.microsoft.com/en-us/dotnet/navigate/migration-guide/)
- [Compatibility](https://learn.microsoft.com/en-us/dotnet/core/compatibility/breaking-changes/)
- [Advanced programming](https://learn.microsoft.com/en-us/dotnet/navigate/advanced-programming/)
- [DevOps and testing](https://learn.microsoft.com/en-us/dotnet/navigate/devops-testing/)
- [Security](https://learn.microsoft.com/en-us/dotnet/navigate/security/)
- [Data access](https://learn.microsoft.com/en-us/dotnet/navigate/data-access/)
- Workloads
- [Web](https://learn.microsoft.com/en-us/aspnet/core/)
- [Cloud](https://learn.microsoft.com/en-us/dotnet/azure/)
- Cloud-native
- [Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/)
- [Microsoft Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/)
- Desktop and mobile
- [.NET Multi-platform App UI (.NET MAUI)](https://learn.microsoft.com/en-us/dotnet/maui/)
- [Windows Presentation Foundation](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/)
- [Windows Forms](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/)
- [WinUI apps](https://learn.microsoft.com/en-us/windows/apps/winui/)
- APIs
- [.NET](https://learn.microsoft.com/en-us/dotnet/api/?view=net-10.0)
- [Aspire](https://learn.microsoft.com/en-us/dotnet/api/?view=dotnet-aspire-9.0)
- [.NET Framework](https://learn.microsoft.com/en-us/dotnet/api/?view=netframework-4.8&preserve-view=true)
- [.NET Multi-platform App UI (.NET MAUI)](https://learn.microsoft.com/en-us/dotnet/api/?view=net-maui-9.0)
- [ASP.NET](https://learn.microsoft.com/en-us/dotnet/api/?view=aspnetcore-10.0)
- [ML.NET](https://learn.microsoft.com/en-us/dotnet/api/?view=ml-dotnet&preserve-view=true)
- [Troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/developer/dotnet/welcome-dotnet-landing)
- Resources
- [Library guidance](https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/)
- [What is .NET?](https://dotnet.microsoft.com/learn/dotnet/what-is-dotnet)
- [.NET architecture guides](https://dotnet.microsoft.com/learn/dotnet/architecture-guides)
- [Learning materials](https://dotnet.microsoft.com/learn)
- [Downloads](https://dotnet.microsoft.com/download)
- [Community](https://dotnet.microsoft.com/platform/community)
- [Support](https://dotnet.microsoft.com/platform/support)
- [Blog](https://devblogs.microsoft.com/dotnet/)
[Download .NET](https://dotnet.microsoft.com/download)
- [.NET fundamentals documentation](https://learn.microsoft.com/en-us/dotnet/fundamentals/)
- Get started
- Overview
- What's new in .NET
- Fundamental coding components
- Runtime libraries
- [Overview](https://learn.microsoft.com/en-us/dotnet/standard/runtime-libraries-overview)
- [Preview APIs](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/preview-apis)
- Format numbers, dates, other types
- Work with strings
- Compliance
- Regular expressions
- Serialization
- System.
CommandLine
- [Overview](https://learn.microsoft.com/en-us/dotnet/standard/commandline/)
- [Get started tutorial](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial)
- [Command-line syntax overview](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax)
- How to
- [Parse and invoke the result](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-parse-and-invoke)
- [Customize parsing and validation](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-customize-parsing-and-validation)
- [Configure the parser](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-configure-the-parser)
- [Enable and customize tab completion](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-enable-tab-completion)
- [Customize help](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-customize-help)
- [Design guidance](https://learn.microsoft.com/en-us/dotnet/standard/commandline/design-guidance)
- [Migration guide to 2.0.0-beta5](https://learn.microsoft.com/en-us/dotnet/standard/commandline/migration-guide-2.0.0-beta5)
- File and stream I/O
- [The System.AppContext class](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-appcontext)
- The System.
Console class
- [The System.Random class](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-random)
- [Artificial intelligence (AI)](https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai?toc=/dotnet/fundamentals/toc.json&bc=/dotnet/breadcrumb/toc.json)
- Dependency injection
- Configuration
- Logging
- [HostBuilder (generic host)](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host)
- [Asynchronous state management](https://learn.microsoft.com/en-us/dotnet/core/extensions/async-state)
- [Testing with FakeTimeProvider](https://learn.microsoft.com/en-us/dotnet/core/extensions/timeprovider-testing)
- [Audit reports for privacy and compliance](https://learn.microsoft.com/en-us/dotnet/core/extensions/audit-reports)
- [Resilience](https://learn.microsoft.com/en-us/dotnet/core/resilience/)
- Networking
- [File globbing](https://learn.microsoft.com/en-us/dotnet/core/extensions/file-globbing)
- [Primitives library](https://learn.microsoft.com/en-us/dotnet/core/extensions/primitives)
- Globalization and localization
- Resources in .NET apps
- Worker services
- [Caching](https://learn.microsoft.com/en-us/dotnet/core/extensions/caching)
- [Channels](https://learn.microsoft.com/en-us/dotnet/core/extensions/channels)
- Math operations
- [The Win32.Registry class](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/microsoft-win32-registry)
- [The Uri class](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-uri)
- Reflection
- Graphics
- [The InternalsVisibleToAttribute class](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-runtime-compilerservices-internalsvisibletoattribute)
- The RuntimeHelpers class
- [The ComponentGuaranteesAttribute class](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-runtime-versioning-componentguaranteesattribute)
- [The AssemblyLoadContext class](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-runtime-loader-assemblyloadcontext)
- The ProcessStartInfo class
- The Environment class
- Execution model
Download PDF
Table of contents
Exit editor mode
1. [Learn](https://learn.microsoft.com/en-us/)
2. [.NET](https://learn.microsoft.com/en-us/dotnet/)
1. [Learn](https://learn.microsoft.com/en-us/)
2. [.NET](https://learn.microsoft.com/en-us/dotnet/)
Ask Learn
Ask Learn
Focus mode
Table of contents
[Read in English](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial)
Add to Collections
Add to plan
[Edit](https://github.com/dotnet/docs/blob/main/docs/standard/commandline/get-started-tutorial.md)
***
#### Share via
[Facebook](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fdotnet%2Fstandard%2Fcommandline%2Fget-started-tutorial%3FWT.mc_id%3Dfacebook) [x.com](https://twitter.com/intent/tweet?original_referer=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fdotnet%2Fstandard%2Fcommandline%2Fget-started-tutorial%3FWT.mc_id%3Dtwitter&tw_p=tweetbutton&url=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fdotnet%2Fstandard%2Fcommandline%2Fget-started-tutorial%3FWT.mc_id%3Dtwitter) [LinkedIn](https://www.linkedin.com/feed/?shareActive=true&text=%0A%0D%0Ahttps%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fdotnet%2Fstandard%2Fcommandline%2Fget-started-tutorial%3FWT.mc_id%3Dlinkedin) [Email](mailto:?subject=%5BShared%20Article%5D%20Tutorial%3A%20Get%20started%20with%20System.CommandLine%20-%20.NET%20%7C%20Microsoft%20Learn&body=%0A%0D%0Ahttps%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fdotnet%2Fstandard%2Fcommandline%2Fget-started-tutorial%3FWT.mc_id%3Demail)
***
Copy Markdown
Print
***
Note
Access to this page requires authorization. You can try [signing in](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial) or [changing directories]().
Access to this page requires authorization. You can try [changing directories]().
# Tutorial: Get started with System.CommandLine
Feedback
Summarize this article for me
## In this article
1. [Prerequisites](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#prerequisites)
2. [Create the app](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#create-the-app)
3. [Test the app](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#test-the-app)
4. [Parse the arguments and invoke the ParseResult](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#parse-the-arguments-and-invoke-the-parseresult)
5. [Add a subcommand and options](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#add-a-subcommand-and-options)
6. [Add subcommands and custom validation](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#add-subcommands-and-custom-validation)
7. [Next steps](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#next-steps)
Show 3 more
This tutorial shows how to create a .NET command-line app that uses the [`System.CommandLine` library](https://learn.microsoft.com/en-us/dotnet/standard/commandline/). You'll begin by creating a simple root command that has one option. Then you'll build on that base, creating a more complex app that contains multiple subcommands and different options for each command.
In this tutorial, you learn how to:
- Create commands, options, and arguments.
- Specify default values for options.
- Assign options and arguments to commands.
- Assign an option recursively to all subcommands under a command.
- Work with multiple levels of nested subcommands.
- Create aliases for commands and options.
- Work with `string`, `string[]`, `int`, `bool`, `FileInfo`, and enum option types.
- Read option values in command action code.
- Use custom code for parsing and validating options.
## Prerequisites
- The latest [.NET SDK](https://dotnet.microsoft.com/download)
- [Visual Studio Code](https://code.visualstudio.com/) editor
- The [C\# DevKit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit)
Or
- [Visual Studio](https://visualstudio.microsoft.com/downloads/?utm_medium=microsoft&utm_source=learn.microsoft.com&utm_campaign=inline+link) with the **.NET desktop development** workload installed.
## Create the app
Create a .NET 9 console app project named "scl".
1. Create a folder named *scl* for the project, and then open a command prompt in the new folder.
2. Run the following command:
.NET CLI
Copy
```
dotnet new console --framework net9.0
```
### Install the System.CommandLine package
- Run the following command:
.NET CLI
Copy
```
dotnet add package System.CommandLine
```
Or, in .NET 10+:
.NET CLI
Copy
```
dotnet package add System.CommandLine
```
### Parse the arguments
Replace the contents of *Program.cs* with the following code:
C\#
Copy
```
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);
}
}
}
```
The preceding code:
- Creates an [option](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax#options) named `--file` of type [FileInfo](https://learn.microsoft.com/en-us/dotnet/api/system.io.fileinfo) and adds it to the [root command](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax#root-command):
C\#
Copy
```
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);
```
- Parses `args` and checks whether any value was provided for the `--file` option. If so, it calls the `ReadFile` method using the parsed value and returns exit code `0`:
C\#
Copy
```
ParseResult parseResult = rootCommand.Parse(args);
if (parseResult.Errors.Count == 0 && parseResult.GetValue(fileOption) is FileInfo parsedFile)
{
ReadFile(parsedFile);
return 0;
}
```
- If no value was provided for `--file`, it prints available parse errors and returns exit code `1`:
C\#
Copy
```
foreach (ParseError parseError in parseResult.Errors)
{
Console.Error.WriteLine(parseError.Message);
}
return 1;
```
- The `ReadFile` method reads the specified file and displays its contents on the console:
C\#
Copy
```
static void ReadFile(FileInfo file)
{
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
}
}
```
## Test the app
You can use any of the following ways to test while developing a command-line app:
- Run the `dotnet build` command, and then open a command prompt in the build output folder to run the executable:
Console
Copy
```
dotnet build
cd bin/Debug/net9.0
scl --file scl.runtimeconfig.json
```
- Use `dotnet run` and pass option values to the app instead of to the `run` command by including them after `--`, as in the following example:
.NET CLI
Copy
```
dotnet run -- --file bin/Debug/net9.0/scl.runtimeconfig.json
```
(This tutorial assumes you're using the first of these options.)
When you run the app, it displays the contents of the file specified by the `--file` option.
Output
Copy
```
{
"runtimeOptions": {
"tfm": "net9.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
}
}
}
```
But if you ask it to display the help by providing `--help`, nothing gets printed to the console. That's because the app doesn't yet handle the scenario where `--file` isn't provided and there are no parse errors.
## Parse the arguments and invoke the ParseResult
System.CommandLine allows you to specify an action that's invoked when a given symbol (command, directive, or option) is parsed successfully. The action is a delegate that takes a [ParseResult](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.parseresult) parameter and returns an `int` exit code. ([Async actions](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-parse-and-invoke#asynchronous-actions) are also available). The exit code is returned by the [ParseResult.Invoke(InvocationConfiguration)](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.parseresult.invoke#system-commandline-parseresult-invoke\(system-commandline-invocationconfiguration\)) method and can be used to indicate whether the command was executed successfully or not.
Replace the contents of *Program.cs* with the following code:
C\#
Copy
```
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);
}
}
}
```
The preceding code:
- Specifies that `ReadFile` is the method that will be called when the root command is **invoked**:
C\#
Copy
```
rootCommand.SetAction(parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
return 0;
});
```
- Parses `args` and **invokes** the result:
C\#
Copy
```
ParseResult parseResult = rootCommand.Parse(args);
return parseResult.Invoke();
```
When you run the app, it displays the contents of the file specified by the `--file` option.
If you ask it to display the help by specifying `scl --help`, the following output gets printed:
Output
Copy
```
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 console
```
[RootCommand](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.rootcommand) by default provides [Help option](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-customize-help#customize-help-output), [Version option](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax#version-option), and [Suggest directive](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax#suggest-directive). The [ParseResult.Invoke(InvocationConfiguration)](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.parseresult.invoke#system-commandline-parseresult-invoke\(system-commandline-invocationconfiguration\)) method is responsible for invoking the action of the parsed symbol. It could be the action explicitly defined for the command or the help action defined by `System.CommandLine` for `System.CommandLine.Help.HelpOption`.
Moreover, when the `Invoke` method detects any parse errors, it prints them to the standard error, prints help to standard output, and returns `1` as the exit code:
Console
Copy
```
scl --invalid bla
```
Output
Copy
```
Unrecognized command or argument '--invalid'.
Unrecognized command or argument 'bla'.
```
## Add a subcommand and options
In this section, you:
- Create more options.
- Create a subcommand.
- Assign the new options to the new subcommand.
The new options let the end user configure the foreground and background text colors and the readout speed. These features will be used to read a collection of quotes that comes from the [Teleprompter console app tutorial](https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/console-teleprompter).
1. Copy the [sampleQuotes.txt](https://github.com/dotnet/samples/blob/main/csharp/getting-started/console-teleprompter/sampleQuotes.txt) file from the dotnet samples repository into your project directory.
(For information on how to download files, see the instructions in [Samples and tutorials](https://learn.microsoft.com/en-us/dotnet/samples-and-tutorials/#view-and-download-samples).)
2. Open the project file and add an `<ItemGroup>` element just before the closing `</Project>` tag:
XML
Copy
```
<ItemGroup>
<Content Include="sampleQuotes.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
```
Adding this markup causes the text file to be copied to the output folder when you build the app. So when you run the executable in that folder, you can access the file by name without specifying a folder path.
3. In *Program.cs*, after the code that creates the `--file` option, create options to control the readout speed and text colors:
C\#
Copy
```
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"
};
```
4. After the line that creates the root command, delete the code that adds the `--file` option to it. You're removing it here because you'll add it to a new subcommand.
5. After the line that creates the root command, create a `read` subcommand. Add the options to this subcommand (by using collection initializer syntax rather than `Options` property), and add the subcommand to the root command.
C\#
Copy
```
Command readCommand = new("read", "Read and display the file.")
{
fileOption,
delayOption,
fgcolorOption,
lightModeOption
};
rootCommand.Subcommands.Add(readCommand);
```
6. Replace the `SetAction` code with the following `SetAction` code for the new subcommand:
C\#
Copy
```
readCommand.SetAction(parseResult => ReadFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(delayOption),
parseResult.GetValue(fgcolorOption),
parseResult.GetValue(lightModeOption)));
```
You're no longer calling `SetAction` on the root command because the root command no longer needs an action. When a command has subcommands, you typically have to specify one of the subcommands when invoking a command-line app.
7. Replace the `ReadFile` action method with the following code:
C\#
Copy
```
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));
}
}
```
The app now looks like this:
C\#
Copy
```
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));
}
}
}
```
### Test the new subcommand
Now if you try to run the app without specifying the subcommand, you get an error message followed by a help message that specifies the subcommand that is available.
Console
Copy
```
scl --file sampleQuotes.txt
```
Output
Copy
```
'--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.
```
The help text for subcommand `read` shows that four options are available. It shows valid values for the enum.
Console
Copy
```
scl read -h
```
Output
Copy
```
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
```
Run subcommand `read` specifying only the `--file` option, and you get the default values for the other three options.
Console
Copy
```
scl read --file sampleQuotes.txt
```
The 42 milliseconds per character default delay causes a slow readout speed. You can speed it up by setting `--delay` to a lower number.
Console
Copy
```
scl read --file sampleQuotes.txt --delay 0
```
You can use `--fgcolor` and `--light-mode` to set text colors:
Console
Copy
```
scl read --file sampleQuotes.txt --fgcolor red --light-mode
```
If you provide an invalid value for `--delay`, you get an error message:
Console
Copy
```
scl read --file sampleQuotes.txt --delay forty-two
```
Output
Copy
```
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.
```
If you provide an invalid value for `--file`, you get an exception:
Console
Copy
```
scl read --file nofile
```
Output
Copy
```
Unhandled exception: System.IO.FileNotFoundException: Could not find file 'C:\bin\Debug\net9.0\nofile''.
File name: 'C:\bin\Debug\net9.0\nofile''
```
## Add subcommands and custom validation
This section creates the final version of the app. When finished, the app will have the following commands and options:
Expand table
| Command | Options | Arguments |
|---|---|---|
| Root command | `--file` (*recursive*) | |
| `quotes` | | |
| `read` | `--delay`, `--fgcolor`, `--light-mode` | |
| `add` | | `quote` and `byline` |
| `delete` | `--search-terms` | |
(A *recursive* option is available to the command it's assigned to and recursively to all its subcommands.)
Here's sample command-line input that invokes each of the available commands with its options and arguments:
Console
Copy
```
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"
```
1. In *Program.cs*, replace the code that creates the `--file` option with the following code:
C\#
Copy
```
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);
}
}
};
```
This code uses [ArgumentResult](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.parsing.argumentresult) to provide custom parsing, validation, and error handling.
Without this code, missing files are reported with an exception and stack trace. With this code just the specified error message is displayed.
This code also specifies a default value, which is why it sets [Option\<T\>.DefaultValueFactory](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.option-1.defaultvaluefactory#system-commandline-option-1-defaultvaluefactory) to custom parsing method.
2. After the code that creates `lightModeOption`, add options and arguments for the `add` and `delete` commands:
C\#
Copy
```
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."
};
```
The [AllowMultipleArgumentsPerToken](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.option.allowmultipleargumentspertoken#system-commandline-option-allowmultipleargumentspertoken) setting lets you omit the `--search-terms` option name when specifying elements in the list after the first one. It makes the following examples of command-line input equivalent:
Console
Copy
```
scl quotes delete --search-terms David "You can do"
scl quotes delete --search-terms David --search-terms "You can do"
```
3. Replace the code that creates the root command and the `read` command with the following code:
C\#
Copy
```
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);
```
This code makes the following changes:
- Removes the `--file` option from the `read` command.
- Adds the `--file` option as a recursive option to the root command.
- Creates a `quotes` command and adds it to the root command.
- Adds the `read` command to the `quotes` command instead of to the root command.
- Creates `add` and `delete` commands and adds them to the `quotes` command.
The result is the following command hierarchy:
Root command βββ`quotes` βββ`read` βββ`add` βββ`delete`
The app now implements the [recommended](https://learn.microsoft.com/en-us/dotnet/standard/commandline/design-guidance#symbols) pattern where the parent command (`quotes`) specifies an area or group, and its children commands (`read`, `add`, `delete`) are actions.
Recursive options are applied to the command and recursively to subcommands. Since `--file` is on the root command, it will be available automatically in all subcommands of the app.
4. After the `SetAction` code, add new `SetAction` code for the new subcommands:
C\#
Copy
```
deleteCommand.SetAction(parseResult => DeleteFromFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(searchTermsOption)));
addCommand.SetAction(parseResult => AddToFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(quoteArgument),
parseResult.GetValue(bylineArgument))
);
```
Subcommand `quotes` doesn't have an action because it isn't a leaf command. Subcommands `read`, `add`, and `delete` are leaf commands under `quotes`, and `SetAction` is called for each of them.
5. Add the actions for `add` and `delete`.
C\#
Copy
```
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}");
}
```
The finished app looks like this:
C\#
Copy
```
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}");
}
}
```
Build the project, and then try the following commands.
Submit a nonexistent file to `--file` with the `read` command, and you get an error message instead of an exception and stack trace:
Console
Copy
```
scl quotes read --file nofile
```
Output
Copy
```
File does not exist
```
If you try to run subcommand `quotes`, you get a message directing you to use `read`, `add`, or `delete`:
Console
Copy
```
scl quotes
```
Output
Copy
```
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.
```
Run subcommand `add`, and then look at the end of the text file to see the added text:
Console
Copy
```
scl quotes add "Hello world!" "Nancy Davolio"
```
Run subcommand `delete` with search strings from the beginning of the file, and then look at the beginning of the text file to see where text was removed:
Console
Copy
```
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
```
Note
The file in the output folder reflects the changes from the `add` and `delete` commands. The copy of the file in the project folder remains unchanged.
## Next steps
In this tutorial, you created a simple command-line app that uses `System.CommandLine`. To learn more about the library, see [System.CommandLine overview](https://learn.microsoft.com/en-us/dotnet/standard/commandline/). To enable tab completion capabilities, see [Tab completion for System.CommandLine](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-enable-tab-completion).
Collaborate with us on GitHub
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, see [our contributor guide](https://learn.microsoft.com/contribute/content/dotnet/dotnet-contribute).
 
.NET feedback
.NET is an open source project. Select a link to provide feedback:
[Open a documentation issue](https://github.com/dotnet/docs/issues/new?template=z-customer-feedback.yml&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fdotnet%2Fstandard%2Fcommandline%2Fget-started-tutorial&pageQueryParams=&contentSourceUrl=https%3A%2F%2Fgithub.com%2Fdotnet%2Fdocs%2Fblob%2Fmain%2Fdocs%2Fstandard%2Fcommandline%2Fget-started-tutorial.md&documentVersionIndependentId=fa160b1d-66ed-8c5d-37cd-b7e02443e9f1&platformId=0f144444-1ce3-cdf7-87a6-c659d44e08d3&feedback=%0A%0A%5BEnter+feedback+here%5D%0A&author=%40gewarren&metadata=*+ID%3A+7705dc41-7297-0807-7e9c-2e6548c26b84%0A*+PlatformId%3A+0f144444-1ce3-cdf7-87a6-c659d44e08d3+%0A*+Service%3A+**dotnet-fundamentals**) [Provide product feedback](https://aka.ms/feedback/report?space=61)
***
## Feedback
Was this page helpful?
Yes
No
No
Need help with this topic?
Want to try using Ask Learn to clarify or guide you through this topic?
Ask Learn
Ask Learn
Suggest a fix?
***
## Additional resources
Training
Module
[Work with files and directories in a .NET app - Training](https://learn.microsoft.com/en-us/training/modules/dotnet-files/?source=recommendations)
Learn how to use .NET, C\#, and System.IO to work with directories, paths, files, and the file system.
***
- Last updated on
12/18/2025
## In this article
1. [Prerequisites](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#prerequisites)
2. [Create the app](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#create-the-app)
3. [Test the app](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#test-the-app)
4. [Parse the arguments and invoke the ParseResult](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#parse-the-arguments-and-invoke-the-parseresult)
5. [Add a subcommand and options](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#add-a-subcommand-and-options)
6. [Add subcommands and custom validation](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#add-subcommands-and-custom-validation)
7. [Next steps](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial#next-steps)
Was this page helpful?
Yes
No
No
Need help with this topic?
Want to try using Ask Learn to clarify or guide you through this topic?
Ask Learn
Ask Learn
Suggest a fix?
## Ask Learn
Preview
Ask Learn is an AI assistant that can answer questions, clarify concepts, and define terms using trusted Microsoft documentation.
Please sign in to use Ask Learn.
[Sign in](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial)
[English (United States)](https://learn.microsoft.com/en-us/locale?target=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fdotnet%2Fstandard%2Fcommandline%2Fget-started-tutorial)
[Your Privacy Choices](https://aka.ms/yourcaliforniaprivacychoices)
Theme
- Light
- Dark
- High contrast
- [AI Disclaimer](https://learn.microsoft.com/en-us/principles-for-ai-generated-content)
- [Previous Versions](https://learn.microsoft.com/en-us/previous-versions/)
- [Blog](https://techcommunity.microsoft.com/t5/microsoft-learn-blog/bg-p/MicrosoftLearnBlog)
- [Contribute](https://learn.microsoft.com/en-us/contribute)
- [Privacy](https://go.microsoft.com/fwlink/?LinkId=521839)
- [Consumer Health Privacy](https://go.microsoft.com/fwlink/?linkid=2259814)
- [Terms of Use](https://learn.microsoft.com/en-us/legal/termsofuse)
- [Trademarks](https://www.microsoft.com/legal/intellectualproperty/Trademarks/)
- Β© Microsoft 2026 |
| Readable Markdown | This tutorial shows how to create a .NET command-line app that uses the [`System.CommandLine` library](https://learn.microsoft.com/en-us/dotnet/standard/commandline/). You'll begin by creating a simple root command that has one option. Then you'll build on that base, creating a more complex app that contains multiple subcommands and different options for each command.
In this tutorial, you learn how to:
- Create commands, options, and arguments.
- Specify default values for options.
- Assign options and arguments to commands.
- Assign an option recursively to all subcommands under a command.
- Work with multiple levels of nested subcommands.
- Create aliases for commands and options.
- Work with `string`, `string[]`, `int`, `bool`, `FileInfo`, and enum option types.
- Read option values in command action code.
- Use custom code for parsing and validating options.
- The latest [.NET SDK](https://dotnet.microsoft.com/download)
- [Visual Studio Code](https://code.visualstudio.com/) editor
- The [C\# DevKit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit)
Or
- [Visual Studio](https://visualstudio.microsoft.com/downloads/?utm_medium=microsoft&utm_source=learn.microsoft.com&utm_campaign=inline+link) with the **.NET desktop development** workload installed.
Create a .NET 9 console app project named "scl".
1. Create a folder named *scl* for the project, and then open a command prompt in the new folder.
2. Run the following command:
```
dotnet new console --framework net9.0
```
- Run the following command:
```
dotnet add package System.CommandLine
```
Or, in .NET 10+:
```
dotnet package add System.CommandLine
```
Replace the contents of *Program.cs* with the following code:
```
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);
}
}
}
```
The preceding code:
- Creates an [option](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax#options) named `--file` of type [FileInfo](https://learn.microsoft.com/en-us/dotnet/api/system.io.fileinfo) and adds it to the [root command](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax#root-command):
```
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);
```
- Parses `args` and checks whether any value was provided for the `--file` option. If so, it calls the `ReadFile` method using the parsed value and returns exit code `0`:
```
ParseResult parseResult = rootCommand.Parse(args);
if (parseResult.Errors.Count == 0 && parseResult.GetValue(fileOption) is FileInfo parsedFile)
{
ReadFile(parsedFile);
return 0;
}
```
- If no value was provided for `--file`, it prints available parse errors and returns exit code `1`:
```
foreach (ParseError parseError in parseResult.Errors)
{
Console.Error.WriteLine(parseError.Message);
}
return 1;
```
- The `ReadFile` method reads the specified file and displays its contents on the console:
```
static void ReadFile(FileInfo file)
{
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
}
}
```
You can use any of the following ways to test while developing a command-line app:
- Run the `dotnet build` command, and then open a command prompt in the build output folder to run the executable:
```
dotnet build
cd bin/Debug/net9.0
scl --file scl.runtimeconfig.json
```
- Use `dotnet run` and pass option values to the app instead of to the `run` command by including them after `--`, as in the following example:
```
dotnet run -- --file bin/Debug/net9.0/scl.runtimeconfig.json
```
(This tutorial assumes you're using the first of these options.)
When you run the app, it displays the contents of the file specified by the `--file` option.
```
{
"runtimeOptions": {
"tfm": "net9.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
}
}
}
```
But if you ask it to display the help by providing `--help`, nothing gets printed to the console. That's because the app doesn't yet handle the scenario where `--file` isn't provided and there are no parse errors.
System.CommandLine allows you to specify an action that's invoked when a given symbol (command, directive, or option) is parsed successfully. The action is a delegate that takes a [ParseResult](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.parseresult) parameter and returns an `int` exit code. ([Async actions](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-parse-and-invoke#asynchronous-actions) are also available). The exit code is returned by the [ParseResult.Invoke(InvocationConfiguration)](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.parseresult.invoke#system-commandline-parseresult-invoke\(system-commandline-invocationconfiguration\)) method and can be used to indicate whether the command was executed successfully or not.
Replace the contents of *Program.cs* with the following code:
```
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);
}
}
}
```
The preceding code:
- Specifies that `ReadFile` is the method that will be called when the root command is **invoked**:
```
rootCommand.SetAction(parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
return 0;
});
```
- Parses `args` and **invokes** the result:
```
ParseResult parseResult = rootCommand.Parse(args);
return parseResult.Invoke();
```
When you run the app, it displays the contents of the file specified by the `--file` option.
If you ask it to display the help by specifying `scl --help`, the following output gets printed:
```
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 console
```
[RootCommand](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.rootcommand) by default provides [Help option](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-customize-help#customize-help-output), [Version option](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax#version-option), and [Suggest directive](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax#suggest-directive). The [ParseResult.Invoke(InvocationConfiguration)](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.parseresult.invoke#system-commandline-parseresult-invoke\(system-commandline-invocationconfiguration\)) method is responsible for invoking the action of the parsed symbol. It could be the action explicitly defined for the command or the help action defined by `System.CommandLine` for `System.CommandLine.Help.HelpOption`.
Moreover, when the `Invoke` method detects any parse errors, it prints them to the standard error, prints help to standard output, and returns `1` as the exit code:
```
scl --invalid bla
```
```
Unrecognized command or argument '--invalid'.
Unrecognized command or argument 'bla'.
```
In this section, you:
- Create more options.
- Create a subcommand.
- Assign the new options to the new subcommand.
The new options let the end user configure the foreground and background text colors and the readout speed. These features will be used to read a collection of quotes that comes from the [Teleprompter console app tutorial](https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/console-teleprompter).
1. Copy the [sampleQuotes.txt](https://github.com/dotnet/samples/blob/main/csharp/getting-started/console-teleprompter/sampleQuotes.txt) file from the dotnet samples repository into your project directory.
(For information on how to download files, see the instructions in [Samples and tutorials](https://learn.microsoft.com/en-us/dotnet/samples-and-tutorials/#view-and-download-samples).)
2. Open the project file and add an `<ItemGroup>` element just before the closing `</Project>` tag:
```
<ItemGroup>
<Content Include="sampleQuotes.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
```
Adding this markup causes the text file to be copied to the output folder when you build the app. So when you run the executable in that folder, you can access the file by name without specifying a folder path.
3. In *Program.cs*, after the code that creates the `--file` option, create options to control the readout speed and text colors:
```
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"
};
```
4. After the line that creates the root command, delete the code that adds the `--file` option to it. You're removing it here because you'll add it to a new subcommand.
5. After the line that creates the root command, create a `read` subcommand. Add the options to this subcommand (by using collection initializer syntax rather than `Options` property), and add the subcommand to the root command.
```
Command readCommand = new("read", "Read and display the file.")
{
fileOption,
delayOption,
fgcolorOption,
lightModeOption
};
rootCommand.Subcommands.Add(readCommand);
```
6. Replace the `SetAction` code with the following `SetAction` code for the new subcommand:
```
readCommand.SetAction(parseResult => ReadFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(delayOption),
parseResult.GetValue(fgcolorOption),
parseResult.GetValue(lightModeOption)));
```
You're no longer calling `SetAction` on the root command because the root command no longer needs an action. When a command has subcommands, you typically have to specify one of the subcommands when invoking a command-line app.
7. Replace the `ReadFile` action method with the following code:
```
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));
}
}
```
The app now looks like this:
```
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));
}
}
}
```
Now if you try to run the app without specifying the subcommand, you get an error message followed by a help message that specifies the subcommand that is available.
```
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.
```
The help text for subcommand `read` shows that four options are available. It shows valid values for the enum.
```
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
```
Run subcommand `read` specifying only the `--file` option, and you get the default values for the other three options.
```
scl read --file sampleQuotes.txt
```
The 42 milliseconds per character default delay causes a slow readout speed. You can speed it up by setting `--delay` to a lower number.
```
scl read --file sampleQuotes.txt --delay 0
```
You can use `--fgcolor` and `--light-mode` to set text colors:
```
scl read --file sampleQuotes.txt --fgcolor red --light-mode
```
If you provide an invalid value for `--delay`, you get an error message:
```
scl read --file sampleQuotes.txt --delay forty-two
```
```
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.
```
If you provide an invalid value for `--file`, you get an exception:
```
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''
```
This section creates the final version of the app. When finished, the app will have the following commands and options:
| Command | Options | Arguments |
|---|---|---|
| Root command | `--file` (*recursive*) | |
| `quotes` | | |
| `read` | `--delay`, `--fgcolor`, `--light-mode` | |
| `add` | | `quote` and `byline` |
| `delete` | `--search-terms` | |
(A *recursive* option is available to the command it's assigned to and recursively to all its subcommands.)
Here's sample command-line input that invokes each of the available commands with its options and arguments:
```
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"
```
1. In *Program.cs*, replace the code that creates the `--file` option with the following code:
```
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);
}
}
};
```
This code uses [ArgumentResult](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.parsing.argumentresult) to provide custom parsing, validation, and error handling.
Without this code, missing files are reported with an exception and stack trace. With this code just the specified error message is displayed.
This code also specifies a default value, which is why it sets [Option\<T\>.DefaultValueFactory](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.option-1.defaultvaluefactory#system-commandline-option-1-defaultvaluefactory) to custom parsing method.
2. After the code that creates `lightModeOption`, add options and arguments for the `add` and `delete` commands:
```
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."
};
```
The [AllowMultipleArgumentsPerToken](https://learn.microsoft.com/en-us/dotnet/api/system.commandline.option.allowmultipleargumentspertoken#system-commandline-option-allowmultipleargumentspertoken) setting lets you omit the `--search-terms` option name when specifying elements in the list after the first one. It makes the following examples of command-line input equivalent:
```
scl quotes delete --search-terms David "You can do"
scl quotes delete --search-terms David --search-terms "You can do"
```
3. Replace the code that creates the root command and the `read` command with the following code:
```
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);
```
This code makes the following changes:
- Removes the `--file` option from the `read` command.
- Adds the `--file` option as a recursive option to the root command.
- Creates a `quotes` command and adds it to the root command.
- Adds the `read` command to the `quotes` command instead of to the root command.
- Creates `add` and `delete` commands and adds them to the `quotes` command.
The result is the following command hierarchy:
Root command βββ`quotes` βββ`read` βββ`add` βββ`delete`
The app now implements the [recommended](https://learn.microsoft.com/en-us/dotnet/standard/commandline/design-guidance#symbols) pattern where the parent command (`quotes`) specifies an area or group, and its children commands (`read`, `add`, `delete`) are actions.
Recursive options are applied to the command and recursively to subcommands. Since `--file` is on the root command, it will be available automatically in all subcommands of the app.
4. After the `SetAction` code, add new `SetAction` code for the new subcommands:
```
deleteCommand.SetAction(parseResult => DeleteFromFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(searchTermsOption)));
addCommand.SetAction(parseResult => AddToFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(quoteArgument),
parseResult.GetValue(bylineArgument))
);
```
Subcommand `quotes` doesn't have an action because it isn't a leaf command. Subcommands `read`, `add`, and `delete` are leaf commands under `quotes`, and `SetAction` is called for each of them.
5. Add the actions for `add` and `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}");
}
```
The finished app looks like this:
```
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}");
}
}
```
Build the project, and then try the following commands.
Submit a nonexistent file to `--file` with the `read` command, and you get an error message instead of an exception and stack trace:
```
scl quotes read --file nofile
```
```
File does not exist
```
If you try to run subcommand `quotes`, you get a message directing you to use `read`, `add`, or `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.
```
Run subcommand `add`, and then look at the end of the text file to see the added text:
```
scl quotes add "Hello world!" "Nancy Davolio"
```
Run subcommand `delete` with search strings from the beginning of the file, and then look at the beginning of the text file to see where text was removed:
```
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
```
Note
The file in the output folder reflects the changes from the `add` and `delete` commands. The copy of the file in the project folder remains unchanged.
In this tutorial, you created a simple command-line app that uses `System.CommandLine`. To learn more about the library, see [System.CommandLine overview](https://learn.microsoft.com/en-us/dotnet/standard/commandline/). To enable tab completion capabilities, see [Tab completion for System.CommandLine](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-enable-tab-completion). |
| Shard | 168 (laksa) |
| Root Hash | 14615152987638977768 |
| Unparsed URL | com,microsoft!learn,/en-us/dotnet/standard/commandline/get-started-tutorial s443 |