-
Notifications
You must be signed in to change notification settings - Fork 380
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
CommandLineStringSplitter does not conform to "standard" quote escaping rules #1758
Comments
Copied from @ptr727's comment on #1781:
|
I hope great @mklement0 review PowerShell/PowerShell#15143 and his module helps to resolve the issue. |
This won't work without breaking supported scenarios. @treenewlyn is asking for a way to escape the quotes in his command, and the existing way of doing that is with backslash, which makes sense. But we currently support tokens that are quoted and have an ending backslash, one example is a windows path that ends in separator. if we introduce the escaping logic that @treenewlyn is asking for, those cases would consider the "ending separator + quote" as a quote and fail. command-line-api/src/System.CommandLine.Tests/Parsing/CommandLineStringSplitterTests.cs Lines 29 to 37 in e965ae2
You can't have both, and since we already have tests showing that windows paths with ending separator are used, I don't know what's the right approach to this. Either do nothing, or take the breaking change. I already tried a couple of times to reconcile both scenarios, but failed. For reference, this is what CMD does with C:\my-app>dotnet run "dotnet publish \"xxx.csproj\" -c Release -o \"./bin/latest\" -r linux-x64 --self-contained false"
foreach (string arg in args): dotnet publish "xxx.csproj" -c Release -o "./bin/latest" -r linux-x64 --self-contained false
Environment.CommandLine: C:\my-app\bin\Debug\net7.0\consoleapp.dll "dotnet publish \"xxx.csproj\" -c Release -o \"./bin/latest\" -r linux-x64 --self-contained false"
Environment.GetCommandLineArgs(2): [C:\my-app\bin\Debug\net7.0\consoleapp.dll,dotnet publish "xxx.csproj" -c Release -o "./bin/latest" -r linux-x64 --self-contained false] And this with C:\my-app>dotnet run "rm -r "c:\temp files\""
foreach (string arg in args): rm -r c:\temp,files"
Environment.CommandLine: C:\my-app\bin\Debug\net7.0\consoleapp.dll "rm -r c:\temp" "files\""
Environment.GetCommandLineArgs(3): [C:\my-app\bin\Debug\net7.0\consoleapp.dll,rm -r c:\temp,files"] And if I escape the quotes C:\my-app>dotnet run "rm -r \"c:\temp files\""
foreach (string arg in args): rm -r "c:\temp files"
Environment.CommandLine: C:\my-app\bin\Debug\net7.0\consoleapp.dll "rm -r \"c:\temp files\""
Environment.GetCommandLineArgs(2): [C:\my-app\bin\Debug\net7.0\consoleapp.dll,rm -r "c:\temp files"] Since -r is meaningful for dotnet cli, I also tried the following C:\my-app>dotnet run "c:\temp files\"
foreach (string arg in args): c:\temp files"
Environment.CommandLine: C:\my-app\bin\Debug\net7.0\consoleapp.dll "c:\temp files\""
Environment.GetCommandLineArgs(2): [C:\my-app\bin\Debug\net7.0\consoleapp.dll,c:\temp files"] If CMD uses |
Console.Write("foreach (string arg in args): ");
for (int i = 0; i < args.Length; i++)
{
Console.Write(args[i]);
if (i < args.Length - 1)
Console.Write(",");
}
Console.WriteLine();
Console.WriteLine();
Console.Write("Environment.CommandLine: ");
Console.Write(Environment.CommandLine);
Console.WriteLine();
Console.WriteLine();
var cmdLineArgs = Environment.GetCommandLineArgs();
Console.Write($"Environment.GetCommandLineArgs({cmdLineArgs.Length}): ");
Console.Write('[' + string.Join(',', cmdLineArgs) + "]");
Console.WriteLine(); |
.NET Runtime can call CommandLineToArgvW here https://github.com/dotnet/runtime/blob/aaf9c8a7702920c4999e0535939e981038bbf42c/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs#L220 CreateProcess in Windows takes a command-line string and the new process decides whether and how to split it to arguments. IIRC, some C library passes a list of argument strings via the cbReserved2 and lpReserved2, but I don't know whether .NET Runtime reads those at all. |
I feel the completion function in the shell should parse quotation marks etc. as needed, and pass to dotnet-suggest a list of strings, the position of the string that contains the insertion point, and the position of the insertion point within that string. It should then read the resulting completions and add any shell-dependent backslashes or quotation marks. That way, dotnet-suggest and the completion sources would not need to support the syntaxes of individual shells. OTOH, if the shell scripting makes this too slow, then it can be reimplemented in dotnet-suggest -- still not in the completion sources. And this should be an optional optimisation; the argv array mechanism should still be available for shells that dotnet-suggest does not explicitly support. |
Pushing these responsibilities to the shell is ideal, but it has a longer timeline. Implementing per-shell solutions should be more performant (and shouldn't even require Other scenarios this method also addresses:
These are a few examples of places where a command line parser is useful outside of the |
The
CommandLineStringSplitter
provides a way to split astring
into astring[]
and is intended to conform to the way that strings are split on the command line.It has two primary use cases:
Parse(string[] args)
orInvoke(string[] args)
can be error prone because it requires people to make assumptions about how a command line will actually be split when your app is started, and the actual rules are not always intuitive.CommandLineStringSplitter
is critical for completions, since the only way to capture the text on the command line from within the launched .NET application is to pass it in as a complete string from the shim shell script and then split it into the equivalentstring[] args
array thatMain
receives.Various shells will exhibit different behaviors here due to different quote escaping rules, so the user's experience of this splitting logic might not match up with a single implementation of
CommandLineStringSplitter
. Because of this, and to eventually provide higher fidelity for the completions experience on different shells in the presence of escapes on the command line, it might be useful to provide multiple implementations over time and we should consider anticipating that need in the API design now.Resources
Related
For the purposes of illustration in examples below, I'll be using the following program to confirm behaviors that differ among various shells:
The text was updated successfully, but these errors were encountered: