Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
javierlarota opened this issue Feb 8, 2022 · 18 comments
Open

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

javierlarota opened this issue Feb 8, 2022 · 18 comments

Comments

@javierlarota
Copy link

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
@jonsequitur
Copy link
Contributor

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

@elgonzo
Copy link
Contributor

elgonzo commented Feb 10, 2022

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

@jonsequitur
Copy link
Contributor

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.

@vlada-shubina
Copy link
Member

@jonsequitur @elgonzo just fyi, this issue was raised for dotnet/templating in dotnet/templating#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.

@jonsequitur jonsequitur added this to the 2.0 GA milestone Mar 21, 2022
@jonsequitur jonsequitur self-assigned this Apr 20, 2022
@jonsequitur
Copy link
Contributor

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?

@KalleOlaviNiemitalo
Copy link

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
Copy link

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
Copy link

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'

@jonsequitur
Copy link
Contributor

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.

@jozkee
Copy link
Member

jozkee commented Oct 31, 2022

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?

@KalleOlaviNiemitalo
Copy link

enable response files for myapp --username=@text

That sounds like an unnecessary regression.

@jonsequitur
Copy link
Contributor

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

@KalleOlaviNiemitalo
Copy link

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

@KalleOlaviNiemitalo
Copy link

KalleOlaviNiemitalo commented Oct 31, 2022

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.

@jonsequitur
Copy link
Contributor

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.

@menees
Copy link

menees commented Sep 20, 2023

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

@elgonzo
Copy link
Contributor

elgonzo commented Sep 20, 2023

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

/// <summary>
/// Response file token replacer, enabled by default.
/// To disable response files support, this property needs to be set to null.
/// </summary>
/// <remarks>
/// When enabled, any token prefixed with <code>@</code> can be replaced with zero or more other tokens. This is mostly commonly used to expand tokens from response files and interpolate them into a command line prior to parsing.
/// </remarks>
public TryReplaceToken? ResponseFileTokenReplacer { get; set; } = StringExtensions.TryReadResponseFile;

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

@menees
Copy link

menees commented Sep 21, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants