如何产生一个进程并在.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();

看起来没问题。

141330 次浏览

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

下面是我验证过的代码,我用它来生成 MSBuild 并监听它的输出:

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

看起来你的两条线路出了故障。在设置事件处理程序以捕获输出之前启动该流程。这个过程可能只是在添加事件处理程序之前完成。

像这样换线。

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

我只是尝试了这个方法,下面的方法对我很有效:

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

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

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

这里有一些完整而简单的代码来实现这一点。

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

注意,这只能捕获标准的 输出; 它不能捕获标准的 错误。如果两者都需要,请对每个流使用 这种技巧

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

这对我来说是可靠的:

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

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

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

我需要捕获 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();
}

我将 stdout 和 stderr 通过管道传递到同一个字符串中,但是如果需要,您可以将它分开。它使用事件,因此应该在事件发生时进行处理(我相信)。我已经成功地运行了它,并将很快进行大容量测试。

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