unstable-doc
only.Expand description
§CLI Concepts
Note: this will be speaking towards the general case.
§Environmental context
When you run a command line application, it is inside a terminal emulator, or terminal. This handles integration with the rest of your system including user input, rendering, etc.
The terminal will run inside of itself an interactive shell.
The shell is responsible for showing the prompt, receiving input including the command you are writing,
letting that command take over until completion, and then repeating.
This is called a read-eval-print loop, or REPL.
Typically the shell will take the command you typed and split it into separate arguments,
including handling of quoting, escaping, and globbing.
The parsing and evaluation of the command is shell specific.
The shell will then determine which application to run and then pass the full command-line as
individual arguments to your program.
These arguments are exposed in Rust as std::env::args_os
.
Windows is an exception in Shell behavior in that the command is passed as an individual
string, verbatim, and the application must split the arguments.
std::env::args_os
will handle the splitting for you but will not handle globs.
Takeaways:
- Your application will only see quotes that have been escaped within the shell
- e.g. to receive
message="hello world"
, you may need to type'message="hello world"'
ormessage=\"hello world\"
- e.g. to receive
- If your applications needs to parse a string into arguments,
you will need to pick a syntax and do it yourself
- POSIX’s shell syntax is a common choice and available in packages like shlex
- See also our REPL cookbook entry
- On Windows, you will need to handle globbing yourself if desired
wild
can help with that
§Parsing
The first argument of std::env::args_os
is the Command::bin_name
which is usually limited to affecting Command::render_usage
.
Command::no_binary_name
and Command::multicall
exist for rare cases when this assumption is not valid.
Command-lines are a context-sensitive grammar, meaning the interpretation of an argument is based on the arguments that came before. Arguments come in one of several flavors:
- Values
- Flags
- Subcommands
When examining the next argument,
- If it starts with a
--
, then that is a long Flag and all remaining text up to a=
or the end is matched to aArg::long
,Command::long_flag
, or alias.- Everything after the
=
is taken as a value and parsing a new argument is examined. - If no
=
is present, then Values will be taken according toArg::num_args
- We generally call a flag that takes a value an “option”
- Everything after the
- If it starts with a
-
, then that is a sequence of short Flags where each character is matched against aArg::short
,Command::short_flag
or alias until=
, the end, or a short Flag takes values (seeArg::num_args
) - If its a
--
, that is an escape and all future arguments are considered to be a Value, even if they start with--
or-
- If it matches a
Command::name
, then the argument is a subcommand - If there is an
Arg
at the nextArg::index
, then the argument is considered a Positional argument
When a subcommand matches,
all further arguments are parsed by that Command
.
There are many settings that tweak this behavior, including:
- [
Arg::last(true)
]: a positional that can only come after--
- [
Arg::trailing_var_arg(true)
]: all further arguments are captured as additional values - [
Arg::allow_hyphen_values(true)
] andArg::allow_negative_numbers
: assumes arguments starting with-
are values and not flags. Command::subcommand_precedence_over_arg
: when anArg::num_args
takes Values, stop if one matches a subCommandCommand::allow_missing_positional
: in limited cases aArg::index
may be skippedCommand::allow_external_subcommands
: treat any unknown argument as a subcommand, capturing all remaining arguments.
Takeaways
- Values that start with a
-
either need to be escaped by the user with--
(if a positional), or you need to set [Arg::allow_hyphen_values(true)
] orArg::allow_negative_numbers
Arg::num_args
,ArgAction::Append
(on a positional),Arg::trailing_var_arg
, andCommand::allow_external_subcommands
all affect the parser in similar but slightly different ways and which to use depends on your application