-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Add ProcessStartInfo.ArgumentList #23347
Comments
I would find a utility method extremely useful as well for properly escaping an argv array into a single string. |
/cc @atsushikan who has been interested in this in the past... |
Few comments:
|
+1 for |
This is the implementation I've been using to escape a single argument, with test cases, based on reading the spec and spending a long time experimentally verifying. https://gist.github.com/jnm2/c5c840bf317605a40f5f56f944db4892 I wonder, is there anything you'd improve in this implementation besides accepting and joining multiple arguments? |
I also think a standalone api for splitting and recombining argument strings would be the way to go. We should really have had that all along instead of welding them into other apis that launch processes or retreive the system command line. Bad separation of concerns there. (Though also having this as part of ProcessStartInfo is justified on the grounds that the Unix implementation shouldn't have to do an unnecessary round-trip just to function.) |
@jnm2 - We have an implementation in corefx (https://github.com/dotnet/corefx/blob/5d3e42f831ec3d4c5964040e235824f779d5db53/src/Common/src/System/PasteArguments.cs) - we're already using it for the Remote Invoke utility in corefx testing. It's probably implementing the same rules but feel free to check for differences. |
I think |
Your implementation passes my tests, so that's cool. =) I think |
@svick:
Indeed, but I think this would be of benefit on Windows too, as it is fair to assume that the more typical use case is to build the desired arguments as a list / array rather than to piece together a single-string command line with intricate quoting. |
Note too that |
Related: https://github.com/dotnet/corefx/issues/21267 |
Maybe the existing implementation for conversion from argument list to single string could be extended, so that tokens containing Reason: Some applications mostly follow the MSVC rules, but additionally allow |
For performance reasons it might be better to update the other private member variable not on assignment, but to generate the complementing representation only if the specific get-accessor is called. This way, the conversion is only performed if necessary. |
@mklement0 are you interested in putting up a formal api proposal for this so that we can submit this up for review? Here is a good example of how they should look: #13979 and here is more info on our proposals: https://github.com/dotnet/corefx/blob/master/Documentation/project-docs/api-review-process.md |
@joperezr: |
@mklement0 - You can edit the formalized proposal into the top comment on this issue rather than creating a new issue. All the discussion is here... My one thought is this: I just know that if this api is surfaced in this form, I'll find myself writing these helper methods over and over again. (I know this because I have been writing these helper methods (without BCL support) over and over again for years!)
Perhaps, the api should just be surfaced in this form rather than being accessible only through Another advantage of doing it this way, is that these two apis could be exposed in |
Absolutely, please surface that API. |
Thanks - I've closed dotnet/corefx#24073 and have updated this issue with its contents. I've also added the need for utility methods and incorporated the signatures of your helper methods as a suggestions. |
ArgumentList
to class ProcessStartInfo
to support predictable passing of arguments by way of an array of literals; also add utility methods for conversion between list and string
ArgumentList
to class ProcessStartInfo
to support predictable passing of arguments by way of an array of literals; also add utility methods for conversion between list and string ArgumentList
to class ProcessStartInfo
to support predictable passing of arguments by way of an array of literals; also add utility methods for conversion between array and single-string forms of arguments
I guess as static methods on the A second candidate is |
Thanks @mklement0 and @atsushikan , marking this ready for review. |
@atsushikan What is the use case for those helper methods? When are the resulting values useful, except for assigning them to the right |
@svick For example, literally everywhere in Cake, escaping is done naively (assuming that tacking a quote onto each end is good enough without understanding the underlying mechanism). People need a go-to proper escaping mechanism. |
@jnm2 Why do you need a conversion method for that? I didn't read the source code of Cake, but wouldn't it be much cleaner to just add elements to an argument list instead of manually escaping each argument and creating a single argument string? I believe this is a perfect example of what @mklement0 said:
So unless there are any nonstandard custom argument-parsing rules involved, I think these helper methods should not be used in that case. (IMHO an argument list is less error-prone than "to piece together a single-string") As I don't really know Cake, I might have missed something - if there is any special reason why Cake can't just use an argument list, I'd be very interested about that! |
There's also an issue of orthogonality. The algorithm has been part of the BCL since forever, but it's always been loaded down with unnecessary fine print - You can only access it for the OS command line, or to generate the arguments passed to the assembly main() method. Now, we're finding new uses for it that wasn't anticipated before and having to go through an api review process that wouldn't be necessarily if the core building blocks had been provided from the start without these artificial restrictions. I want to end that cycle here. |
You can easily work this around by using collection initialization which I'd argue is even more readable than using var info = new ProcessStartInfo("bash")
{
Arguments = {
"-c",
"echo hi"
}
}; The primary reason to use |
So folks who build and pass around the arguments in a |
I appreciate the explanation. The syntax you demonstrate is indeed great for initializing from a literal and as simple as it gets. The ability to initialize from a preexisting collection with something like |
That's fair but how often will this likely be the case in practice? In my own experience, I have centralized the process creation and string mangling in one place. And that's the place where I'd construct the startup info. |
I'm thinking of things like Cake and process utility classes I've written for build scripts and integration testing. But an extension method will go a long way and |
@terrajobst: Good point; I had actually not realized that the initializer even works with non-literal elements: var info = new ProcessStartInfo("bash")
{
ArgumentList = {
"-c",
// using an expression here (including a variable) works perfectly fine
"echo " + Environment.GetEnvironmentVariable("USER")
}
}; Thinking more broadly, though (and I'm happy to take this tangent where it belongs if it has any merit), should a |
I found this issue after a long search on how to spawn a process in dotnet (I'm new to the platform.) If I'm reading the code and the tests correctly, the current API is supposed to be used like this: ProcessStartInfo psi = new ProcessStartInfo("filename");
psi.ArgumentList.Add("arg1");
psi.ArgumentList.Add("arg2");
// and so on, requiring a loop to pass an arbitrary array of arguments! I would like to express that from the outside, this looks like a very convoluted and hidden way to set the arguments for a process execution, especially since the artificial and error-prone |
@tobia @terrajobst already addressed this issue in his comment
and you can still use the initializer non-literal elements: var info = new ProcessStartInfo("bash")
{
ArgumentList = {
"-c",
// using an expression here (including a variable) works perfectly fine
"echo " + Environment.GetEnvironmentVariable("USER")
}
}; or you can just provide the
In windows, you need to provide the arguments as a string while starting the process. The main purpose of adding the cc @danmosemsft |
I see this in documentation but not in any libraries on Mac. |
What do you mean by that? What exactly happens? |
Net4.7.2, netstandard2.0, netcoreapp2.2 ProcessStartInfo does not have an ArgumentList property. |
That is not .NET Core version, but list of TFMs. The fist two won't have it, the last one will. |
For any improvement in Arguments / ArgumentsList, please make sure it will include the support for parsing single quotes in the argument value (It's fine it has to be escaped). For the current it doesn't seem to be possible :-( |
@MrM40 Single quotes don't have to be escaped in general as far as I'm aware. The rules being followed aren't the rules of a specific shell like PowerShell but rather the rules of the https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments convention (on Windows). |
But still, the single quotes aren't parsed, escaped or non-escaped :-( |
@MrM40 Single quotes should be treated like any other character. At least, that's the long-standing argv convention on Windows. The only special characters are |
That would be a compatibility-breaking solution, because everyone is "okay"-ish with the way the command lines have been parsed for last years. For any new cross-platform development, you really really better use the new API with And about your request, if we ever want to make any improvements to the API, we better add support for new extendable "parsing engines" (for *nix) and "concatting engines" (for Windows), so everybody could put their own solution into there. On *nix, there's actually no "preferred" way of parsing the command lines to argument lists, and each shell (or other sort of app) is free to implement their own way of passing/escaping the arguments. On Windows, there's a "standard" way, but not every program follows it (one notable exception is e.g. Explorer who doesn't like the strings with this format: So, in fact, you'd need multiple parsing/concatting engines when passing arguments to a "standard" Windows program, a Cygwin program, an MSYS program, a WSL program etc. etc. etc. |
I agree you would expect that single quotes would be parsed without problems, but I will claim they are not. See #35222 |
@MrM40 The last suggestion at https://github.com/dotnet/corefx/issues/35222#issuecomment-476518970 is that you should use double quotes. If that didn't work, can you follow up in that issue? |
There are programs/commands which require single quotes in the argument, I hope we can agree to that. And for that reason they should be parsed. |
@MrM40 could you please specify your target platform first? Otherwise, the following discussion doesn't make much sense. |
@MrM40 As @TSlivede points out in https://github.com/dotnet/corefx/issues/35222#issuecomment-514244283, this passes three separate arguments to ls: You want |
Two examples with solution: In the Linux shell:
In the Linux shell:
|
@MrM40 and what's the issue? These samples you've posted do something (I'm not sure about the correctness of the first one though because of the way you've removed the You're saying that your samples work fine "in the Linux shell". If you require the escape/argument parser of the particular shell, then you better use that exact shell to do the work you require. For example,
It will do the processing, Could you please explain why either that or the new |
@ForNeVeR: Sorry for the short-info, I've rephrased the example. |
Alright! No need to apologize, you've done nothing wrong. You had a question and you asked. Thanks for collaboration! |
Note: Updated into an API proposal, with feedback incorporated.
Rationale
On Unix platforms, external executables (binaries) receive their arguments as an array of string literals, which makes for robust, predictable passing of arguments (see Linux system function
execvp()
, for instance).Regrettably, in Windows there is no equivalent mechanism: a pseudo shell "command line" must be passed as a single string encoding all arguments, and it is up to the target executable itself to parse that line into individual arguments.
The
ProcessStartInfo
class currently only supports the Windows approach directly, by exposing astring Arguments
property that expects the whole command line.On Unix platforms, this means that even if you start out with an array of arguments, you must currently artificially assemble its elements into a single pseudo shell command line, only to have CoreFX split that back into an array of individual arguments behind the scenes so as to be able to invoke the platform-native process-creation function, which takes an array of arguments.
Not only is this an unnecessary burden on the user and inefficient, it is error-prone. It is easy to accidentally assemble a command-line string that does not round-trip as expected.
As a real-world use case, consider the ongoing quoting woes PowerShell experiences.
At least on Unix platforms, PowerShell should be able to simply pass the arguments that are the result of its parsing as-is to external utilities.
Having the ability to pass an array of arguments would be of benefit on Windows too, as it is fair to assume that the more typical use case is to build the desired arguments as a list / array rather than to piece together a single-string command line with intricate quoting.
(The latter should only be needed if you're passing an preexisting string through or if you're invoking an executable that has custom argument-parsing rules.)
Proposed API
Add a
IReadOnlyList<String> ArgumentList
property (conceptually, an array of argument string literals) to theProcessStartInfo
class, to complement the existingstring Arguments
(pseudo shell command line) property, and let each update the other lazily, on demand, when accessed:If
.Arguments
was (last) assigned to, do the following when.ArgumentList
is accessed:Call
ParseArgumentsIntoList()
, which splits the string into individual arguments based on the rules for MS C++ programs and return the resulting list.If
.ArgumentList
was (last) assigned to, do the following when.Arguments
is accessed:Synthesize the pseudo shell command line from the individual arguments and assign the result to using the above rules in reverse (enclose in
"..."
if a given argument has embedded whitespace, ..., as already implemented for internal use inSystem.PasteArguments.Paste()
) and return the resultingstring
.'
(single quotes) lest they be interpreted as having syntactic function, which to some programs they do (e.g., Ruby, Cygwin).That way, both
.Arguments
and.ArgumentList
can be assigned to, and the respective other property contains the equivalent in terms of the official (MS C++) parsing rules.A
ProcessStartInfo
instance constructed this way can therefore be used on all supported platforms:On Windows, pass the
.Arguments
property value to theCreateProcess()
/ShellExecuteEx()
Windows API functions, as before.On Unix platforms, pass the
.ArgumentList
property value via.ToArray()
toForkAndExecProcess()
.Additionally, to complement the suggested behind-the-scenes conversion between the array form and the single-string form, public utility methods should be implemented that perform these conversions explicitly, on demand.
@atsushikan proposes the following signatures:
Open Questions
internal
(System.PasteArguments
); @atsushikan suggests making them public methods of theSystem.Diagnostics.Process
class.Usage
The text was updated successfully, but these errors were encountered: