How do I launch a subprocess in C# with an argv? (Or convert agrv to a legal arg string)
Asked Answered
S

5

8

I have a C# command-line application that I need to run in windows and under mono in unix. At some point I want to launch a subprocess given a set of arbitrary paramaters passed in via the command line. For instance:

Usage: mycommandline [-args] -- [arbitrary program]

Unfortunately, System.Diagnostics.ProcessStartInfo only takes a string for args. This is a problem for commands such as:

./my_commandline myarg1 myarg2 -- grep "a b c" foo.txt

In this case argv looks like :

argv = {"my_commandline", "myarg1", "myarg2", "--", "grep", "a b c", "foo.txt"}

Note that the quotes around "a b c" are stripped by the shell so if I simply concatenate the arguments in order to create the arg string for ProcessStartInfo I get:

args = "my_commandline myarg1 myarg2 -- grep a b c foo.txt"

Which is not what I want.

Is there a simple way to either pass an argv to subprocess launch under C# OR to convert an arbitrary argv into a string which is legal for windows and linux shell?

Any help would be greatly appreciated.

Sturges answered 2/6, 2010 at 17:42 Comment(3)
Just a note: C# on Windows doesn't have this problem because Windows doesn't technically have an argv. The entire command line is passed to the process, and the splitting up of the arguments is the job of the new process, not the OS. Unix takes the opposite approach: the OS is responsible for passing an array of strings, so things like wildcard expansion can be handled by the calling process.Auntie
@DanielPryden that isn't true. windows programs have an argv list like POSIX ones. .Net just unhelpfully hides this list. Rather irritating if you ask me.Embodiment
@IanNorton: On the contrary, CreateProcess only takes a single argument string. The Microsoft C Runtime (MSVCRT) parses that string and makes it available as argv, but programs are not required to use the CRT implementation, and the parsing that the CRT does is not guaranteed to return the same set of tokens that were used to launch the process.Auntie
S
1

Thanks to all for the suggestions. I ended up using the algorithm from shquote (http://www.daemon-systems.org/man/shquote.3.html).

/**
 * Let's assume 'command' contains a collection of strings each of which is an
 * argument to our subprocess (it does not include arg0).
 */
string args = "";
string curArg;
foreach (String s in command) {
    curArg = s.Replace("'", "'\\''"); // 1.) Replace ' with '\''
    curArg = "'"+curArg+"'";          // 2.) Surround with 's
    // 3.) Is removal of unnecessary ' pairs. This is non-trivial and unecessary
    args += " " + curArg;
}

I've only tested this on linux. For windows you can just concatenate the args.

Sturges answered 3/6, 2010 at 17:53 Comment(0)
A
1

MSDN has a description of how the MS Visual C Runtime parses the string returned by GetCommandLine() into an argv array.

You might also be interested in the list2cmdline() function from the Python standard library that is used by Python's subprocess module to emulate the Unix argv behavior in a Win32 environment.

Auntie answered 2/6, 2010 at 19:6 Comment(0)
E
1

In windowsland, it's simple really...add extra quotation marks in the string you pass to the System.Diagnostics.ProcessStartInfo object.

e.g. "./my_commandline" "myarg1 myarg2 -- grep \"a b c\" foo.txt"

Efficacious answered 3/6, 2010 at 15:45 Comment(1)
Should work fine in all environments; it's what I use in cygwin. Make sure your string is actually using escaped backslashes - so, literally in your code, you would have @"myarg1 myarg2 -- grep \""a b c\"" foo.txt" or "myarg1 myarg2 -- grep \\\"a b c \\\" foo.txt".Mainsail
S
1

Thanks to all for the suggestions. I ended up using the algorithm from shquote (http://www.daemon-systems.org/man/shquote.3.html).

/**
 * Let's assume 'command' contains a collection of strings each of which is an
 * argument to our subprocess (it does not include arg0).
 */
string args = "";
string curArg;
foreach (String s in command) {
    curArg = s.Replace("'", "'\\''"); // 1.) Replace ' with '\''
    curArg = "'"+curArg+"'";          // 2.) Surround with 's
    // 3.) Is removal of unnecessary ' pairs. This is non-trivial and unecessary
    args += " " + curArg;
}

I've only tested this on linux. For windows you can just concatenate the args.

Sturges answered 3/6, 2010 at 17:53 Comment(0)
D
0

You will need to run a new subprocess using grep and all arguments that grep will be needing.

void runProcess(string processName, string args)
{
    using (Process p = new Process())
    {
        ProcessStartInfo info = new ProcessStartInfo(processName);
        info.Arguments = args;
        info.RedirectStandardInput = true;
        info.RedirectStandardOutput = true;
        info.UseShellExecute = false;
        p.StartInfo = info;
        p.Start();
        string output = p.StandardOutput.ReadToEnd();
        // process output
    }
}

then make a call to runProcess("grep", "a", "b", "c", "foo.txt");

Edit: Updated args handling.

Dint answered 2/6, 2010 at 18:20 Comment(2)
This won't work if any arguments in the args array contain whitespace. Also, from your calling syntax, did you mean to declare args as a params array? Because the example as you have it can't be used the way you show.Auntie
@Daniel Pryden You're correct about the params, and I suppose it would be up to the developer to modify this script to the point of using it in his own code. This post should be seen more as a general guide for how to launch a subprocess than a fully-functioning program to be integrated verbatim into your current system.Dint
S
0

Just use a Regex to check if a string has spaces of any kind, and replace the original string with a new one with quotes:

using System.Text.RegularExpressions;
// ...
for(int i=0; i<argv.Length; i++) {
    if (Regex.IsMatch(i, "(\s|\")+")) {
        argv[i] = "\"" + argv[i] + "\"";
        argv[i].Replace("\"", "\\\"");
    }
}
Single answered 3/6, 2010 at 10:39 Comment(2)
This won't work if the string has quotes in it: ie "a \" b" produces the arg, a " b, which if surrounded by quotes is "a " b " which is both wrong and an illegal string.Sturges
Well, then you just need to expand the regex, and add a string.replace.Single

© 2022 - 2024 — McMap. All rights reserved.