如何在.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();