如何在.NET中产生一个进程并捕获它的STDOUT?

我需要产生一个控制台应用程序的子进程,并捕获它的输出。

我写了一个方法的以下代码:

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; 

但是,这不会返回任何东西。 我不相信OutputDataReceived事件被回调,或WaitForExit()命令可能阻塞线程,所以它永远不会回调。

任何建议?

编辑:看起来像我试图与回调太辛苦。 这样做:

 return p.StandardOutput.ReadToEnd(); 

看起来工作正常。

这是我验证过的代码。 我用它来产生MSBuild并监听它的输出:

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

我只是试了这个东西,下面的工作对我来说:

 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(); 

看起来你的两条线路是不合适的。 在设置事件处理程序来捕获输出之前,您需要启动该过程。 在添加事件处理程序之前,这个过程有可能完成。

像这样切换线。

 p.OutputDataReceived += ... p.Start(); 

我需要捕获标准输出和标准错误,并且如果进程没有在预期的时候退出,超时。 我想出了这个:

 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(); } 

我将stdout和stderr管道输入到同一个字符串中,但是如果需要的话可以保持分开。 它使用事件,所以它应该处理他们来(我相信)。 我已经成功运行了,并将很快进行批量测试。

这里有一些完整和简单的代码来做到这一点。 当我使用它时,这工作得很好。

 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(); 

请注意,这只能捕获标准输出 ; 它不捕获标准错误 。 如果你想同时使用这种技术 ,每个流。

在设置StartInfo之后,您需要调用p.Start()来实际运行该进程。 实际上,你的函数可能挂在WaitForExit()调用上,因为这个过程从来没有真正开始过。

重定向流是异步的,在流程终止后可能会继续。 在进程终止process.CancelOutputRead()之后,由Umar提到取消process.CancelOutputRead() 。 但是,这有数据损失的潜力。

这对我来说是可靠的工作:

 process.WaitForExit(...); ... while (process.StandardOutput.EndOfStream == false) { Thread.Sleep(100); } 

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

 if (process.WaitForExit(timeout)) { process.WaitForExit(); } 

以下是我用来运行进程并获取输出和错误的方法:

 public static string ShellExecute(this string path, string command, TextWriter writer, params string[] arguments) { using (var process = Process.Start(new ProcessStartInfo { WorkingDirectory = path, FileName = command, Arguments = string.Join(" ", arguments), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true })) { using (process.StandardOutput) { writer.WriteLine(process.StandardOutput.ReadToEnd()); } using (process.StandardError) { writer.WriteLine(process.StandardError.ReadToEnd()); } } return path; } 

例如 :

 @"E:\Temp\MyWorkingDirectory".ShellExecute(@"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe", Console.Out); 

由于应用程序在第一个BeginOutputReadLine();之后退出,Judah的回答对我来说不起作用(或者不完整BeginOutputReadLine();

这对我来说是一个完整的片段,读取一个ping的输出:

  var process = new Process(); process.StartInfo.FileName = "ping"; process.StartInfo.Arguments = "google.com -t"; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.UseShellExecute = false; process.OutputDataReceived += (sender, a) => Console.WriteLine(a.Data); process.Start(); process.BeginOutputReadLine(); process.WaitForExit();