[Mono-dev] sub-process invocation on posix

Greg Najda gregnajda at gmail.com
Tue Jun 11 00:59:34 UTC 2013


The Windows CreateProcess<http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425%28v=vs.85%29.aspx>function
takes command line arguments as a single string. This detail
leaked into the .NET Process class. Windows programs with a
WinMain<http://msdn.microsoft.com/en-us/library/windows/desktop/ms633559%28v=vs.85%29.aspx>entry
point typically break that argument string into arguments using
CommandLineToArgvW<http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391%28v=vs.85%29.aspx>.
With a regular "main" entry point, the C runtime does that for you.
Unfortunately there is no ArgvToCommandLine function, which is a shame
because CommandLineToArgvW has pretty funky rules for quotes and
backslashes. See the docs for
CommandLineToArgvW<http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391%28v=vs.85%29.aspx>and
Raymond Chen's blog
post <http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx>for
info. Simply enclosing in quotes and putting a backslash before quotes
and backslashes is not good enough for Windows.

I was curious about this myself a week or two ago because I had to pass
some dynamic arguments to a process so I dove into the Mono source. On
Windows Mono passes the argument string as is to CreateProcess. On Unix
platforms Mono uses the GNOME
g_shell_parse_argv()<https://developer.gnome.org/glib/2.34/glib-Shell-related-Utilities.html#g-shell-parse-argv>function
to convert the arg string into an argv before starting the process.

Feel free to use the following code taken from a personal project of
mine<https://bitbucket.org/LHCGreg/dbsc/src/c3cca47e6b190f7b6fad47c12d781e445e962acc/mydbsc/MySqlDbscEngine.cs?at=master>.
It passes the unit tests I threw it.

        private string QuoteCommandLineArg(string arg)
        {
            if (Environment.OSVersion.Platform == PlatformID.Unix ||
Environment.OSVersion.Platform == PlatformID.MacOSX)
            {
                return QuoteCommandLineArgUnix(arg);
            }
            else
            {
                return QuoteCommandLineArgWindows(arg);
            }
        }

        internal static string QuoteCommandLineArgWindows(string arg)
        {
            // If a double quotation mark follows two or an even number of
backslashes,
            // each proceeding backslash pair is replaced with one
backslash and the double quotation mark is removed.
            // If a double quotation mark follows an odd number of
backslashes, including just one,
            // each preceding pair is replaced with one backslash and the
remaining backslash is removed;
            // however, in this case the double quotation mark is not
removed.
            // -
http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx
            //
            // Windows command line processing is funky

            string escapedArg;
            Regex backslashSequenceBeforeQuotes = new Regex(@"(\\+)""");
            // Double \ sequences before "s, Replace " with \", double \
sequences at end
            escapedArg = backslashSequenceBeforeQuotes.Replace(arg, (match)
=> new string('\\', match.Groups[1].Length * 2) + "\"");
            escapedArg = escapedArg.Replace("\"", @"\""");
            Regex backslashSequenceAtEnd = new Regex(@"(\\+)$");
            escapedArg = backslashSequenceAtEnd.Replace(escapedArg, (match)
=> new string('\\', match.Groups[1].Length * 2));
            // C:\blah\"\\
            // "C:\blah\\\"\\\\"
            escapedArg = "\"" + escapedArg + "\"";
            return escapedArg;
        }

        internal static string QuoteCommandLineArgUnix(string arg)
        {
            // Mono uses the GNOME g_shell_parse_argv() function to convert
the arg string into an argv
            // Just prepend " and \ with \ and enclose in quotes.
            // Much simpler than Windows!

            Regex backslashOrQuote = new Regex(@"\\|""");
            return "\"" + backslashOrQuote.Replace(arg, (match) => @"\" +
match.ToString()) + "\"";
        }


Hope that helps.

- Greg


On Mon, Jun 10, 2013 at 3:46 PM, Ian Norton <inorton at gmail.com> wrote:

> I kind of already have a thing to do that, feels a bit icky though,
> especially as there must be some thing lower down that undoes the joined up
> string into a char** again. :)
>
>
> On 10 June 2013 16:06, Michael Hutchinson <m.j.hutchinson at gmail.com>wrote:
>
>> FWIW, you actually just need to double quote each argument and escape
>> double quotes so you can very easily write a helper to do this in a way
>> that works on both Mono and .NET:
>>
>> static Process StartProcess (string name, params string[] args)
>> {
>>     string a = null;
>>     if (args != null && args.Length > 0)
>>         a = "\"" + string.Join (args.Select (s => s.Replace ("\"",
>> "\\\"")).ToArray (), "\" \"") + "\"";
>>     return Process.Start (
>>         new ProcessStartInfo (name, a) {
>>             ShellExecute = false,
>>         }
>>     );
>> }
>>
>> Obviously this could be done more efficiently with a StringBuilder.
>>
>> Apologies for any errors, I'm writing this on my phone...
>>
>> - Michael
>> On Jun 6, 2013 1:18 PM, "Ian Norton" <inorton at gmail.com> wrote:
>>
>>>  Hiya, I'm aware that I can use Process.Start() but I'd really really
>>> like to be able to directly pass a list of strings to my child process as
>>> arguments rather than having to escape shell characters and spaces etc.
>>>
>>> Ie, In perl or C I'd do:
>>>
>>> system("df","-m","/home/foo/Documents/Pictures/My Holiday");
>>>
>>> Where in c# I'm forced to escape the space -> "My\ Holiday"
>>>
>>> Ian
>>>
>>> _______________________________________________
>>> Mono-devel-list mailing list
>>> Mono-devel-list at lists.ximian.com
>>> http://lists.ximian.com/mailman/listinfo/mono-devel-list
>>>
>>>
>
> _______________________________________________
> Mono-devel-list mailing list
> Mono-devel-list at lists.ximian.com
> http://lists.ximian.com/mailman/listinfo/mono-devel-list
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ximian.com/pipermail/mono-devel-list/attachments/20130610/e8736642/attachment-0001.html>


More information about the Mono-devel-list mailing list