如何在.NET中生成进程并捕获其STDOUT?

Fly*_*wat 133 .net c# process spawning

我需要生成一个作为控制台应用程序的子进程,并捕获其输出.

我为方法编写了以下代码:

string retMessage = String.Empty;
ProcessStartInfo startInfo = new ProcessStartInfo();
Process p = new Process();

startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;

startInfo.UseShellExecute = false;
startInfo.Arguments = command;
startInfo.FileName = exec;

p.StartInfo = startInfo;
p.Start();

p.OutputDataReceived += new DataReceivedEventHandler
(
    delegate(object sender, DataReceivedEventArgs e)
    {
        using (StreamReader output = p.StandardOutput)
        {
            retMessage = output.ReadToEnd();
        }
    }
);

p.WaitForExit();

return retMessage;
Run Code Online (Sandbox Code Playgroud)

但是,这不会返回任何内容.我不相信该OutputDataReceived事件被回调,或者该WaitForExit()命令可能阻塞该线程,因此它永远不会回调.

有什么建议?

编辑:看起来我在回调中努力了.这样做:

return p.StandardOutput.ReadToEnd(); 
Run Code Online (Sandbox Code Playgroud)

似乎工作正常.

Jud*_*ngo 153

这是我已经验证可以使用的代码.我用它来产生MSBuild并监听它的输出:

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (sender, args) => Console.WriteLine("received output: {0}", args.Data);
process.Start();
process.BeginOutputReadLine();
Run Code Online (Sandbox Code Playgroud)

  • 修复OP的关键命令是添加BeginOutputReadLine() (32认同)
  • 要捕获错误,添加`RedirectStandardError = true`和`process.ErrorDataReceived + =(sender,args)=> Console.WriteLine(args.Data);`和`process.BeginErrorReadLine();` (7认同)
  • 我无法相信有多少人遗漏了"BeginOutputReadLine"部分.救了我的日子,谢谢! (5认同)
  • 好奇,Start()和BeginOutputReadLine()之间有时间,输出可能会丢失(不给OutputDataReceived)? (3认同)

Uma*_*aja 35

我刚尝试了这件事,以下内容对我有用:

StringBuilder outputBuilder;
ProcessStartInfo processStartInfo;
Process process;

outputBuilder = new StringBuilder();

processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.UseShellExecute = false;
processStartInfo.Arguments = "<insert command line arguments here>";
processStartInfo.FileName = "<insert tool path here>";

process = new Process();
process.StartInfo = processStartInfo;
// enable raising events because Process does not raise events by default
process.EnableRaisingEvents = true;
// attach the event handler for OutputDataReceived before starting the process
process.OutputDataReceived += new DataReceivedEventHandler
(
    delegate(object sender, DataReceivedEventArgs e)
    {
        // append the new data to the data already read-in
        outputBuilder.Append(e.Data);
    }
);
// start the process
// then begin asynchronously reading the output
// then wait for the process to exit
// then cancel asynchronously reading the output
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();

// use the output
string output = outputBuilder.ToString();
Run Code Online (Sandbox Code Playgroud)

  • 我发现这稍微容易一点`p.StartInfo = startInfo; p.Start(); output = p.StandardOutput.ReadToEnd(); p.WaitForExit();` (7认同)
  • @Spike,但同步运行.如果您需要将数据输入标准输入,那么这将不起作用 (4认同)
  • 请注意,EnableRaisingEvents = true似乎只需要引发Process.Exited事件 (2认同)

Rob*_*ler 22

我需要捕获stdout和stderr并且如果进程在预期时没有退出则超时.我想出了这个:

Process process = new Process();
StringBuilder outputStringBuilder = new StringBuilder();

try
{
process.StartInfo.FileName = exeFileName;
process.StartInfo.WorkingDirectory = args.ExeDirectory;
process.StartInfo.Arguments = args;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = false;
process.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.ErrorDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var processExited = process.WaitForExit(PROCESS_TIMEOUT);

if (processExited == false) // we timed out...
{
    process.Kill();
    throw new Exception("ERROR: Process took too long to finish");
}
else if (process.ExitCode != 0)
{
    var output = outputStringBuilder.ToString();
    var prefixMessage = "";

    throw new Exception("Process exited with non-zero exit code of: " + process.ExitCode + Environment.NewLine + 
    "Output from process: " + outputStringBuilder.ToString());
}
}
finally
{                
process.Close();
}
Run Code Online (Sandbox Code Playgroud)

我正在将stdout和stderr连接到相同的字符串中,但如果需要,可以将它分开.它使用事件,所以它应该处理它们(我相信).我已成功运行,并将很快进行批量测试.


Sam*_*Sam 21

这里有一些完整而简单的代码.我使用它时工作正常.

var processStartInfo = new ProcessStartInfo
{
    FileName = @"C:\SomeProgram",
    Arguments = "Arguments",
    RedirectStandardOutput = true,
    UseShellExecute = false
};
var process = Process.Start(processStartInfo);
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
Run Code Online (Sandbox Code Playgroud)

请注意,这仅捕获标准输出 ; 它没有捕获标准错误.如果您想要两者,请对每个流使用此技术.


Jar*_*Par 20

看起来你的两条线路无序.您在设置事件处理程序以捕获输出之前启动该过程.在添加事件处理程序之前,该过程可能刚刚完成.

像这样切换线.

p.OutputDataReceived += ...
p.Start();        
Run Code Online (Sandbox Code Playgroud)


jws*_*jws 5

重定向流是异步的,并且在进程终止后可能会继续。Umar 提到要在进程终止后取消process.CancelOutputRead()。然而,这有可能丢失数据。

这对我来说工作可靠:

process.WaitForExit(...);
...
while (process.StandardOutput.EndOfStream == false)
{
    Thread.Sleep(100);
}
Run Code Online (Sandbox Code Playgroud)

我没有尝试过这种方法,但我喜欢 Sly 的建议:

if (process.WaitForExit(timeout))
{
    process.WaitForExit();
}
Run Code Online (Sandbox Code Playgroud)

  • 当读取输出/错误流时,在 process.WaitForExit(timeout) 返回 true 后异步调用 process.WaitForExit() (无参数版本)。这将阻塞,直到到达输出/错误流的末尾。有关详细信息,请参阅此处的备注:http://msdn.microsoft.com/en-us/library/ty0d8k56%28v=vs.110%29 (5认同)

Soa*_*Box 3

设置 StartInfo 后,您需要调用 p.Start() 来实际运行该进程。事实上,您的函数可能挂在 WaitForExit() 调用上,因为该进程从未真正启动。