command-line-api icon indicating copy to clipboard operation
command-line-api copied to clipboard

Does not allow arguments that start with the '@' character

Open javierlarota opened this issue 3 years ago • 18 comments

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

javierlarota avatar Feb 08 '22 17:02 javierlarota

This is intentional. The @ character indicates a response file. https://github.com/dotnet/command-line-api/blob/main/docs/Features-overview.md#response-files

jonsequitur avatar Feb 09 '22 20:02 jonsequitur

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);

elgonzo avatar Feb 10 '22 18:02 elgonzo

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 avatar Feb 11 '22 02:02 jonsequitur

@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.

vlada-shubina avatar Mar 21 '22 15:03 vlada-shubina

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?

jonsequitur avatar Apr 20 '22 22:04 jonsequitur

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.

KalleOlaviNiemitalo avatar Jul 01 '22 16:07 KalleOlaviNiemitalo

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 @.

KalleOlaviNiemitalo avatar Jul 01 '22 16:07 KalleOlaviNiemitalo

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'

KalleOlaviNiemitalo avatar Jul 01 '22 16:07 KalleOlaviNiemitalo

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.

jonsequitur avatar Sep 02 '22 22:09 jonsequitur

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?

jozkee avatar Oct 31 '22 18:10 jozkee

enable response files for myapp --username=@text

That sounds like an unnecessary regression.

KalleOlaviNiemitalo avatar Oct 31 '22 20:10 KalleOlaviNiemitalo

It's an unintended behavior so I think it probably makes sense to make it consistent now instead of maintaining it forever.

jonsequitur avatar Oct 31 '22 21:10 jonsequitur

How is the @@ escaping going to be handled in file name completion?

KalleOlaviNiemitalo avatar Oct 31 '22 21:10 KalleOlaviNiemitalo

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.

KalleOlaviNiemitalo avatar Oct 31 '22 21:10 KalleOlaviNiemitalo

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.

jonsequitur avatar Oct 31 '22 22:10 jonsequitur

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 avatar Sep 20 '23 17:09 menees

@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).

elgonzo avatar Sep 20 '23 18:09 elgonzo

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.

menees avatar Sep 21 '23 12:09 menees