command-line-api
command-line-api copied to clipboard
Does not allow arguments that start with the '@' character
static void Main(string param1, string param2)
{
Console.WriteLine($"Parameters Param1={param1}, Param2={param2}");
}
Fails when invoking the app from command line with a parameter that starts with the "@" character
C:\MyApp.exe --param1 Hello --param2 @World
Response file not found 'World'
Required argument missing for option: --param2
This is intentional. The @
character indicates a response file. https://github.com/dotnet/command-line-api/blob/main/docs/Features-overview.md#response-files
To add to @jonsequitur's answer, it is possible to disable response files (and thus treating @World
like an ordinary string value) by using CommandLineBuilder. Basically something like this (untested, could contain errors):
using System.CommandLine.Builder;
using System.CommandLine.Parser;
var myRootCmd = ... your normal command(s) setup ...
var parser = new CommandLineBuilder(myRootCmd)
.ParseResponseFileAs(ResponseFileHandling.Disabled)
.UseDefaults()
.Build();
parser.Invoke(args);
There's an interesting question here about how the user rather than the tool developer should specify an argument beginning with "@" when response files are enabled. There should be a way to escape the string but I'm not sure there's one implemented.
@jonsequitur @elgonzo just fyi, this issue was raised for dotnet/templating in https://github.com/dotnet/templating/issues/4488 as well. In one of the templates, the author uses username
option which may start with @
. We need to suggest an alternative to escape it.
We don't want to lose response files for dotnet new
at this point.
I've been unable to find prior art for escaping a token on the command line that could indicate a response file. Suggestions are welcome. I'll put forward an approach we could take in the absence of a better-established convention.
We looked a bit at escaping using quotes but this tends to get quite confusing when combined with shell escaping requirements, so we discarded it.
My proposal that could be a little easier for users is to prepend an additional @
. So in the example where you have a user whose username is @jonsequitur
, you could pass this to avoid indicating a response file:
> myapp --username @@jonsequitur
Of course, communicating the existence of this convention would be a whole other challenge.
Thoughts?
myapp --username=@text
doesn't treat text
as the name of a response file. This is a workaround that already works.
I feel even myapp --username @text
should not treat text
as the name of a response file, if the --username
option is known to have arity 1 or greater. But I don't know if someone is relying on the current behavior.
Possibly a directive could be used… myapp [noresponsefiles] @text
This would be difficult for users to discover, and cumbersome to use interactively. For scripts though, this would be easier than doubling the @
sign, because the script author could just add [noresponsefiles]
unconditionally rather than check whether an argument starts with @
.
communicating the existence of this convention would be a whole other challenge.
Could be made part of the error message.
Response file not found 'text'. To specify the argument '@text' without referring to a response file, double the @ sign like this: '@@text'
These are all good suggestions, though I worry that interpreting the @
prefix contextually would be difficult to reason about. Right now we inline the replacement tokens very mechanically, and this happens before parsing begins. It's easy to understand.
A simple escape mechanism and an error message would be pretty simple and effective, I think.
The proposed solution is to enable response files for myapp --username=@text
and at the same time make @@
the way to escape arguments that start with @
.
How does that sound?
enable response files for myapp --username=@text
That sounds like an unnecessary regression.
It's an unintended behavior so I think it probably makes sense to make it consistent now instead of maintaining it forever.
How is the @@ escaping going to be handled in file name completion?
I mean, System.CommandLine does not currently complete file names and instead leaves that to the shell. But the shell doesn't know about @@ escaping so you'd need to handle it either in the shell-specific completion function or in the System.CommandLine library. If response files are implemented in middleware that can be disabled or replaced by the application, I think this middleware should take care of the @@ escaping as well. So the completion callback of the argument outputs unescaped strings, the middleware escapes @ characters, and either the shell-specific completion function or the shell itself escapes shell metacharacters such as dollar signs.
There's a separate issue to improve filename completions (#1697). We'd like System.CommandLine to do a better job at filename completions rather than the current all-or-nothing approach where you get either completions from the parser or from the file system but not both. In any case, the shell isn't typically handling response files anyway.
Since response file expansion happens during tokenization, prior to parsing, the contents of the response file are taken into account when calculating completions. This happens before middleware. The response file expansion is still configurable, but the initial trigger to perform replacement (the @
prefix) is not. I'd like to avoid adding more complexity here because it makes it harder for end users to understand.
If anyone using Beta 4 arrives here, you'll find that ParseResponseFileAs(ResponseFileHandling.Disabled)
mentioned in @elgonzo's answer is gone. The Beta 4-compatible way to treat @-prefixed arguments as something other than response files is documented in issue #1750's first comment under "Custom token replacement".
You'll need to create a CommandLineBuilder
and then call the UseTokenReplacer
extension method to get the raw @-prefixed token. Then you can return false to tell the parser to treat the token as a raw string:
.UseTokenReplacer((string tokenToReplace, out IReadOnlyList<string>? replacementTokens, out string? errorMessage) =>
{
replacementTokens = null;
errorMessage = null;
return false;
})
@menees
appreciate the follow-up.
But since then, the code base of System.CommandLine has changed significantly. CommandLineBuilder is no more, and response file handling is now being configured via CliConfiguration.ResponseFileTokenReplacer
:
https://github.com/dotnet/command-line-api/blob/a045dd54a4c44723c215d992288160eb1401bb7f/src/System.CommandLine/CliConfiguration.cs#L71-L78
Basically, now you would disable response file handling by setting up a CliConfiguration like that for example:
var cliConf = new CliConfiguration(myRootCommand)
{
ResponseFileTokenReplacer = null
};
var parseResult = cliConf.Parse(args);
or
cliConf.Invoke(args); // or await cliConf.InvokeAsync(args, cancellationToken);
Note that the nuget.org feed does not yet provide up-to-date builds of System.CommandLine. Up-to-date builds (more or less) of System.CommandLine are available through the daily builds nuget feed mentioned in the projects readme.md: https://github.com/dotnet/command-line-api#daily-builds. But be aware that the library is still under heavy development, and expect System.CommandLine's APIs possibly still changing (until either the project maintainers announce the APIs having become stable or a proper non-beta is released).
Thanks for the update @elgonzo.
I already dealt with a bunch of changes going from Beta1 through Beta4. I've been watching this repo for a few years now, and I've noticed (with dread) the massive API churn from Beta4 to present. I kind of regret taking a dependency on this library as early as I did, but I liked the high-level features and architecture discussed in the March 2019 MSDN article. I also liked that it was from Microsoft and part of the dotnet project. I was excited when Beta4 was documented in MSDN/Learn because it seemed like the library was getting close to release. Alas, no.
At this point, I'm just sticking with Beta4 until I encounter something it doesn't handle or until System.CommandLine 2.0 is stable and out of beta.