If you've gone through the tutorial, you'll already have some idea of how Ookii.CommandLine parses arguments. This page will explain the rules in detail, including all the possible kinds or arguments.
Command line arguments are passed to your application when it is started, and are typically accessed
through the parameter of the static void Main(string[] args)
method. This provides the arguments
as an array of strings, which Ookii.CommandLine will parse to extract strongly-typed, named values
that you can easily access in your application.
The method used to extract values from the array of string arguments is determined by the command line argument parsing rules. Ookii.CommandLine supports two sets of parsing rules: the default mode, which uses parsing rules similar to those used by PowerShell, and long/short mode, which is more POSIX-like, and lets arguments have a long name and a short name, with different prefixes. Most of the below information applies to both modes, with the differences described at the end.
In Ookii.CommandLine, all command line arguments have a name, and can be assigned a value on the command line using that name. They follow the name of your application's executable on the command prompt, and typically take the following form:
-ArgumentName value
The argument name is preceded by the argument name prefix. This prefix is configurable, but
defaults to accepting a dash (-
) and a forward slash (/
) on Windows, and only a dash (-
) on
other platforms such as Linux or MacOS.
Argument names are case insensitive by default, though this can be customized using the
ParseOptionsAttribute.CaseSensitive
property or the ParseOptions.ArgumentNameComparer
property.
The argument's value follow the name, separated by either white space (as a separate argument token),
or by the argument name/value separator, which is a colon (:
) by default. The following is
identical to the previous example:
-ArgumentName:value
Whether white-space is allowed to separate the name and value is configured using the
ParseOptionsAttribute.AllowWhiteSpaceValueSeparator
or
ParseOptions.AllowWhiteSpaceValueSeparator
property, and the argument name/value separator can
be customized using the ParseOptionsAttribute.NameValueSeparator
or
ParseOptions.NameValueSeparator
property.
Not all arguments require values; those that do not are called switch arguments and have a value determined by their presence or absence on the command line.
An argument can have one or more aliases: alternative names that can also be used to supply the same
argument. For example, an argument named -Verbose
might use the alias -v
as a shorter to type
alternative.
An argument can be positional, which means in addition to being supplied by name, it can also be supplied without the name, using the position of the value. Which argument the value belongs to is determined by its position relative to other positional arguments.
If an argument value is encountered without being preceded by a name, it is matched to the next positional argument without a value. For example, take the following command line arguments:
value1 –ArgumentName value2 value3
In this case, value1 is not preceded by a name; therefore, it is matched to the first positional
argument. Value2 follows a name, so it is matched to the argument with the name -ArgumentName
.
Finally, value3 is matched to the second positional argument.
A positional argument can still be supplied by name. If a positional argument is supplied by name,
it cannot also be specified by position; in the previous example, if the argument named
-ArgumentName
was the second positional argument, then value3 becomes the value for the third
positional argument, because the value for -ArgumentName
was already specified by name. If
-ArgumentName
is the first positional argument, this would cause an error (unless duplicate
arguments are allowed in the options), because it already had a value set by value
.
A command line argument that is required must be supplied on all invocations of the application. If a required argument is not supplied, this is considered an error and parsing will fail.
Any argument can be made required. Usually, it is recommended for any required argument to also be a positional argument, but this is not mandatory.
For positional arguments, required arguments must always come before optional arguments; it is not allowed to define a required positional argument after an optional positional argument.
A switch argument, sometimes also called a flag, is an argument with a Boolean type (bool
). Its
value is determined by its presence or absence on the command line; the value will be true if the
argument is supplied, and false if not. The following sets the switch argument named “Switch” to
true:
-Switch
A switch argument’s value can be specified explicitly, as in the following example:
-Switch:false
You must use the name/value separator (a colon by default) to specify an explicit value for a switch
argument; you cannot use white space. If the command line contains -Switch false
, then false
is
the value of the next positional argument, not the value for -Switch
.
If you use a nullable Boolean type (bool?
) as the type of the argument, it will be null
if
not supplied, true
if supplied, and false
only if explicitly set to false using -Switch:false
.
Some arguments can take multiple values; these are multi-value arguments. These arguments can be supplied multiple times, and each value is added to the set of values. For example, consider the following command line arguments:
-ArgumentName value1 –ArgumentName value2 –ArgumentName value3
In this case, if -ArgumentName
is a multi-value argument, the value of the argument will be a list
holding all three values.
It’s possible to specify a separator for multi-value arguments using the
MultiValueSeparatorAttribute
attribute. This makes it possible to specify multiple values for the
argument while the argument itself is specified only once. For example, if the separator is set to a
comma, you can specify the values as follows:
-ArgumentName value1,value2,value3
In this case, the value of the argument named -ArgumentName
will be a list with the three values "value1", "value2" and "value3".
Note: if you specify a separator for a multi-value argument, it is not possible to have an argument value containing the separator. There is no way to escape the separator. Therefore, make sure you pick a separator that will never be used in the argument values, and be extra careful with culture-sensitive argument types.
You can also use the MultiValueSeparatorAttribute
to indicate the argument allows white-space
separated values, which means it will consume multiple argument tokens that follow it.
-ArgumentName value1 value2 value3
This has the same effect as above, with all three values treated as values for the one argument. A multi-value argument that uses this option will take all following values until another argument name is encountered. That means that you cannot specify positional arguments until you've used another named argument.
If a multi-value argument is positional, it must be the last positional argument. All remaining positional argument values will be considered values for the multi-value argument.
If a multi-value argument is required, it means it must have at least one value. You cannot set a default value for an optional multi-value argument.
If the type of the argument is a list of Boolean values (e.g. bool[]
), it will act as a
multi-value argument and a switch. A value of true (or the explicit value if one is given) gets
added to the list for every time that the argument is supplied.
If an argument is not a multi-value argument, it is an error to supply it more than once, unless
duplicate arguments are allowed in the ParseOptions
or ParseOptionsAttribute
, in which
case only the last value is used. It's also possible to emit a warning for duplicate values using
the ParseOptions
.
Dictionary arguments are multi-value arguments that specify a set of key/value pairs. Each value for
a dictionary argument takes the form key=value
, like in the following example:
-ArgumentName key1=value1 –ArgumentName key2=value2
In this case, the value of the argument named -ArgumentName
will be a dictionary with two keys,
"key1" and "key2", with the associated values "value1" and "value2" respectively.
If you specify the same key more than once, an exception will be thrown, unless the
AllowDuplicateDictionaryKeysAttribute
attribute is specified for the argument.
The default key/value separator (which is =
) can be overridden using the KeyValueSeparatorAttribute
attribute.
Ookii.CommandLine allows you to define arguments with any .Net type, including types such as
String
, Int32
, DateTime
, FileInfo
, Uri
, any enumeration type, and many more.
Any type can be used; the only requirement is that it is possible to convert a string value to that
type.
Ookii.CommandLine will try to convert the argument using the following options, in order of preference:
- If the argument has the
TypeConverterAttribute
applied, the specified customTypeConverter
. - The argument type's default
TypeConverter
, if it can convert from a string. - A
public static Parse(String, ICultureInfo)
method. - A
public static Parse(String)
method. - A public constructor that takes a single string argument.
This will cover the majority of types you'd want to use for arguments without having to write any
conversion code. If you write your own custom type, you can use it for arguments as long as it meets
one of the above criteria (a TypeConverter
is preferred).
It is possible to override the default conversion by specifying a custom type converter using the
System.ComponentModel.TypeConverterAttribute
. When this attribute is applied to an argument,
the specified type converter will be used for conversion instead of any of the default methods.
The default TypeConverter
for enumeration types uses case insensitive conversion, and allows
both the names and underlying value of the enumeration to be used. This means that e.g. for the
DayOfWeek
enumeration, "Monday", "monday", and "1" can all be used to indicate
DayOfWeek.Monday
.
In the case of a numeric value, the converter does not check if the resulting value is valid for
the enumeration type, so again for DayOfWeek
, a value of "9" would be converted to (DayOfWeek)9
even though there is no such value in the enumeration.
To ensure the result is constrained to only the defined values of the enumeration, use the
ValidateEnumValueAttribute
validator.
The converter allows the use of comma-separated values, which will be combined using a bitwise or
operation. This is allowed regardless of whether or not the FlagsAttribute
attribute is present on
the enumeration, which can have unexpected results. Using the DayOfWeek
example again,
"Monday,Tuesday" would result in the value DayOfWeek.Monday | DayOfWeek.Tuesday
, which is actually
equivalent to DayOfWeek.Wednesday
.
One way to avoid this is to use the following pattern validator, which ensures that the string value before conversion does not contain a comma:
[ValidatePattern("^[^,]*$")]
You can also use a pattern like "^[a-zA-Z]"
to ensure the value starts with a letter, to disallow
the use of numeric values entirely.
For multi-value and dictionary arguments, the converter must be for the element type (e.g. if the
argument is a multi-value argument of type int[]
, the type converter must be able to convert to
int
). For a dictionary argument the element type is KeyValuePair<TKey, TValue>
, and the type
converter is responsible for parsing the key and value from the argument value.
Ookii.CommandLine provides the KeyValuePairConverter<TKey, TValue>
class that is used by default
for dictionary arguments. You can override this using the TypeConverterAttribute
as usual, but
if you only want to customize the parsing of the key and value types, you can use the
KeyTypeConverterAttribute
and the ValueTypeConverterAttribute
attributes respectively.
The KeyValuePairConverter<TKey, TValue>
will use those attributes to locate a custom converter.
You can also customize the key/value separator used by this converter using the
KeyValueSeparatorAttribute
attribute.
If you do specify the TypeConverterAttribute
for a dictionary argument, the
KeyTypeConverterAttribute
, ValueTypeConverterAttribute
, and KeyValueSeparatorAttribute
attributes will be ignored.
For many types, the conversion can be culture dependent. For example, converting numbers or dates
depends on the CultureInfo
class, which defines the accepted formats and how they’re
interpreted; for example, some cultures might use a period as the decimal separator, while others
use a comma.
To ensure a consistent parsing experience for all users regardless of their machine's regional
format settings, Ookii.CommandLine defaults to using CultureInfo.InvariantCulture
. You can change
this using the ParseOptions.Culture
property, but be very careful if you do.
Ookii.CommandLine provides support for nullable reference types. Not only is the library itself
fully annotated, but if you use the .Net 6.0 version of the library, command line argument parsing
takes into account the nullability of the argument types. If the argument is declared with a
nullable reference or value type (e.g. string?
or int?
), nothing changes. But if the argument is
not nullable (e.g. string
(in a context with NRT support) or int
), CommandLineParser
will
ensure that the value will not be null.
Assigning a null value to an argument only happens if the TypeConverter
for that argument returns
null
as the result of the conversion. If this happens and the argument is not nullable, a
CommandLineArgumentException
is thrown with the category set to NullArgumentValue
.
Null-checking for non-nullable reference types is only available in .Net 6.0 and later. If you are using the .Net Standard versions of Ookii.CommandLine, this check is only done for value types.
For multi-value arguments, the nullability check applies to the type of the elements (e.g.
string?[]
for an array), and for dictionary arguments, it applies to the value (e.g.
Dictionary<string, string?>
); the key may never be null for a dictionary argument.
See also the CommandLineArgument.AllowNull
property.
POSIX and GNU conventions specify that options use a dash (-
) followed by a single characters, and
define the concept of long options, which use --
followed by an a multi-character name. This style
is used by many tools like dotnet
, git
, and many others, and may be preferred if you are writing
a cross-platform application.
Ookii.CommandLine calls this style of parsing "long/short mode," and offers it as an alternative
mode to augment the default parsing rules. In this mode, an argument can have the regular long name
and an additional single-character short name, each with its own argument name prefix. By default,
the prefix --
is used for long names, and -
(and /
on Windows) for short names.
POSIX conventions also specify the use of lower case argument names, with dashes separating words
("dash-case"), which you can easily achieve using name transformation,
and case-sensitive argument names, which can be enabled with the
ParseOptionsAttribute.CaseSensitive
property or the ParseOptions.ArgumentNameComparer
property.
For example, an argument named --path
could have a short name -p
. It could then be supplied
using either name:
--path value
Or:
-p value
Note that you must use the correct prefix: using -path
or --p
will not work.
An argument can have either a short name or a long name, or both.
Arguments in this mode can still have aliases. You can set separate long and short aliases, which follow the same rules as the long and short names.
For switch arguments with short names, the switches can be combined in a single argument. For
example, given the switches -a
, -b
and -c
, the following command line sets all three switches:
-abc
This is equivalent to:
-a -b -c
This only works for switch arguments, and does not apply to long names.
Besides these differences, long/short mode follows the same rules and conventions as the default mode outlined above, with all the same options.
Next, let's take a look at how to define arguments.