如何在C#中启动一个带有argv的子进程?(或将agrv转换为合法的参数字符串)

8

我有一个C#命令行应用程序,需要在Windows和Unix中的mono下运行。 在某个时候,我想启动一个子进程,并通过命令行传递一组任意参数。例如:

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

很遗憾,System.Diagnostics.ProcessStartInfo只接受一个字符串作为参数。这对于像下面这样的命令是有问题的:

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

在这种情况下,argv看起来像是:
argv = {"my_commandline", "myarg1", "myarg2", "--", "grep", "a b c", "foo.txt"}

请注意,shell会删除“a b c”周围的引号,因此如果我简单地按顺序连接参数以创建ProcessStartInfo的arg字符串,则会得到以下结果:
args = "my_commandline myarg1 myarg2 -- grep a b c foo.txt"

这不是我想要的。

有没有一种简单的方法可以将argv传递给C#下启动的子进程,或者将任意argv转换为在Windows和Linux shell中合法的字符串?

任何帮助都将不胜感激。


1
只是一个提示:在 Windows 上的 C# 并没有这个问题,因为 Windows 从技术上讲并没有 argv。整个命令行被传递给进程,参数的拆分是新进程的工作,而不是操作系统的工作。Unix 则采取相反的方法:操作系统负责传递字符串数组,因此调用过程可以处理诸如通配符扩展之类的事情。 - Daniel Pryden
@DanielPryden 这不是真的。Windows程序有像POSIX一样的argv列表。.Net只是不方便地隐藏了这个列表。如果你问我,这相当恼人。 - IanNorton
1
相反,CreateProcess 只接受一个字符串参数。Microsoft C 运行时库 (MSVCRT) 解析该字符串并将其作为 argv 变量提供,但程序不必使用 CRT 实现,并且 CRT 所做的解析不能保证返回启动进程所使用的相同令牌集合。 - Daniel Pryden
5个回答

1
感谢大家提供的建议。最终,我采用了来自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;
}

我只在Linux上测试过这个。对于Windows,您可以将参数连接起来。


1

1
在Windows环境下,这很简单...只需在传递给System.Diagnostics.ProcessStartInfo对象的字符串中添加额外的引号即可。
例如:"./my_commandline" "myarg1 myarg2 -- grep \"a b c\" foo.txt"

应该在所有环境中都能正常工作;这是我在cygwin中使用的方法。确保您的字符串实际上使用了转义反斜杠 - 因此,在您的代码中,您将使用@"myarg1 myarg2 -- grep ""a b c"" foo.txt"或"myarg1 myarg2 -- grep \"a b c \" foo.txt"。 - Matt DeKrey

0

你需要使用grep和所有grep所需的参数来运行一个新的子进程。

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
    }
}

然后调用runProcess("grep", "a", "b", "c", "foo.txt");

编辑:更新args处理。


2
如果args数组中的任何参数包含空格,则此方法将无法正常工作。另外,根据您的调用语法,您是否意味着将args声明为params数组?因为按照您展示的示例,它无法以您展示的方式使用。 - Daniel Pryden
@Daniel Pryden,你对参数的理解是正确的,我想开发者需要根据自己的代码修改这个脚本。这篇文章应该被视为一个启动子进程的通用指南,而不是一个完全可直接集成到你当前系统中的程序。 - Jake

0
只需使用正则表达式检查字符串是否有任何空格,并用带引号的新字符串替换原始字符串即可:
using System.Text.RegularExpressions;
// ...
for(int i=0; i<argv.Length; i++) {
    if (Regex.IsMatch(i, "(\s|\")+")) {
        argv[i] = "\"" + argv[i] + "\"";
        argv[i].Replace("\"", "\\\"");
    }
}

如果字符串中包含引号,这种方法将不起作用:例如,"a " b" 会产生参数,即 a " b,如果被引号包围,则是 "a " b ",这既是错误的,也是非法的字符串。 - lucas
那么,您只需要扩展正则表达式,并添加一个字符串替换。 - supercheetah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接