【问题标题】:How to spawn a process and capture its STDOUT in .NET? [duplicate]如何在 .NET 中生成进程并捕获其 STDOUT? [复制]
【发布时间】:2010-09-22 02:16:24
【问题描述】:

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

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

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

看起来工作正常。

【问题讨论】:

  • 如果您不打算与应用程序交互而只关心其输出,则不应使用异步BeginOutputReadLine()Start() 的方式来执行此操作。我发现这些不是很可靠,它们有时会截断应用程序输出的开头。
  • 为什么不retMessage = e.Data?这已经是一个字符串变量了:)

标签: c# .net process spawning


【解决方案1】:

这是我已经验证可以工作的代码。我用它来生成 MSBuild 并监听它的输出:

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

【讨论】:

  • 修复 OP 的关键命令是添加 BeginOutputReadLine()
  • 非常感谢,@Judah Himango
  • 我不敢相信有多少人忽略了“BeginOutputReadLine”部分。节省了我的时间,谢谢!
  • 若要同时捕获错误,请添加RedirectStandardError = trueprocess.ErrorDataReceived += (sender, args) => Console.WriteLine(args.Data);process.BeginErrorReadLine();
  • 很好奇,Start() 和 BeginOutputReadLine() 之间是否有时间可能会丢失输出(未提供给 OutputDataReceived)?
【解决方案2】:

我刚刚尝试了这个,以下对我有用:

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.StartInfo = startInfo; p.Start(); output = p.StandardOutput.ReadToEnd(); p.WaitForExit();
  • @Spike,但它是同步运行的。如果您需要将数据输入标准输入,那么这将不起作用
  • 请注意,EnableRaisingEvents = true 似乎只需要引发 Process.Exited 事件
  • 这很好用。谢谢@umar-farooq-khawaja
  • 应该是“AppendLine”
【解决方案3】:

这里有一些完整而简单的代码来执行此操作。当我使用它时效果很好。

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

请注意,这只捕获标准输出;它不捕获标准错误。如果两者都需要,请为每个流使用this technique

【讨论】:

  • 这是最简单的答案
【解决方案4】:

如果进程没有按预期退出,我需要同时捕获 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 用管道传输到同一个字符串中,但如果需要,您可以将其分开。它使用事件,所以它应该在它们到来时处理它们(我相信)。我已经成功运行了,很快就会进行批量测试。

【讨论】:

  • 这个对我有用!谢谢
【解决方案5】:

您的两条线路似乎有问题。在设置事件处理程序以捕获输出之前启动该过程。在添加事件处理程序之前,该过程可能刚刚完成。

像这样换行。

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

【讨论】:

  • 即使没有这样标记,这可能是正确的答案。
  • 这对我不起作用。 FlySwat 在他的回答中包含的编辑对我有用。
  • @CasperLeonNielsen,同上。
  • 这是错误的。要完成这项工作,您首先需要调用BeginOutputReadLine,它开始读取stdOut 流。您也可以直接读取流。不幸的是,implementation 仅在进程为started 时创建 IO 流。调用BeginOutputReadLine 或在调用process.Start() 之前从流中读取将抛出,因为流尚不存在。
【解决方案6】:

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

【讨论】:

  • 这是一个错字,因为我正在删除一些代码来制作示例,我不小心把那行删掉了。
【解决方案7】:

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

这对我来说很可靠:

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

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

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

【讨论】:

【解决方案8】:

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

这对我来说是一个完整的 sn-p,读取 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();

【讨论】:

  • 很棒的例子。
【解决方案9】:

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

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

【讨论】:

  • 这不是等待发生的死锁吗? MSDN Docs 说如果同时收听输出和错误,您将面临死锁的风险。如果错误缓冲区已满,应用程序将停止,并等待它清空。但是在输出缓冲区完成之前你不会清空错误缓冲区(它不会因为应用程序正在等待错误缓冲区)...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-18
  • 1970-01-01
相关资源
最近更新 更多