[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