从.NET应用程序(C#)捕获控制台输出

153

我该如何在我的.NET应用程序中调用控制台应用程序并捕获控制台生成的所有输出?

(记住,我不想先将信息保存在文件中,然后再重新列出,因为我希望能够实时接收它。)


可能是Process.start:如何获取输出?的重复问题。 - Michael Freidgeim
3
请参见两个问题的日期并查看哪一个是“重复”的。 - Gripsoft
1
“可能是重复问题”是一种清理方式 - 关闭类似的问题并保留最佳答案。日期并不重要。请参见我应该投票关闭一个重复的问题,即使它更新得更多,并且有最新的答案吗? 如果您认为需要澄清,请在为“可能是重复问题”的自动评论添加澄清链接上投票。 - Michael Freidgeim
8个回答

185

使用ProcessStartInfo.RedirectStandardOutput属性可以轻松实现此目的。完整示例包含在链接的MSDN文档中;唯一的注意事项是您可能还需要重定向标准错误流以查看应用程序的所有输出。

Process compiler = new Process();
compiler.StartInfo.FileName = "csc.exe";
compiler.StartInfo.Arguments = "/r:System.dll /out:sample.exe stdstr.cs";
compiler.StartInfo.UseShellExecute = false;
compiler.StartInfo.RedirectStandardOutput = true;
compiler.Start();    

Console.WriteLine(compiler.StandardOutput.ReadToEnd());

compiler.WaitForExit();

4
如果您不想在结尾处添加额外的换行符,只需使用Console.Write即可。 - tm1
3
需要注意的是,如果您在控制台应用程序中与用户输入提示功能一起使用ReadToEnd(),则可能会导致内存泄漏。例如:覆盖文件:Y或N?等等。由于进程在等待用户输入时永远不会退出,因此会导致ReadToEnd方法出现问题。 更安全的捕获输出的方法是使用"process.OutputDataReceived"事件处理程序,让进程通知您的应用程序准备好接收输出。 - Baaleos
如何在代码部署到Azure Web应用程序时进行捕获,因为compiler.StartInfo.FileName =“csc.exe”;可能不存在! - Asif Iqbal
如何在代码部署到 Azure Web 应用程序时捕获 if,因为 compiler.StartInfo.FileName = "csc.exe"; 可能不存在! - Asif Iqbal

58

这是比@mdb的答案有所改进的地方。具体来说,我们还捕获了进程的错误输出。此外,我们通过事件来捕获这些输出,因为如果要捕获错误和常规输出,则ReadToEnd()不起作用。我花了一段时间才让它工作,因为实际上在Start()之后还需要调用BeginxxxReadLine()

异步方式:

using System.Diagnostics;

Process process = new Process();

void LaunchProcess()
{
    process.EnableRaisingEvents = true;
    process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_OutputDataReceived);
    process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_ErrorDataReceived);
    process.Exited += new System.EventHandler(process_Exited);

    process.StartInfo.FileName = "some.exe";
    process.StartInfo.Arguments = "param1 param2";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.RedirectStandardOutput = true;

    process.Start();
    process.BeginErrorReadLine();
    process.BeginOutputReadLine();          

    //below line is optional if we want a blocking call
    //process.WaitForExit();
}

void process_Exited(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("process exited with code {0}\n", process.ExitCode.ToString()));
}

void process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.WriteLine(e.Data + "\n");
}

void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.WriteLine(e.Data + "\n");
}

2
这是易于理解、优美的代码。我唯一的小问题是你不必要地添加了新行。"writeline" 会为你添加一个,因此你捕获的输出每隔一行就插入了一个空行。 - John Lord
有没有同步的方式来做这件事? - DMX David Cardinal

13

10

ConsoleAppLauncher 是一个专门回答这个问题的开源库。它捕获控制台生成的所有输出,并提供简单的界面来启动和关闭控制台应用程序。

每次控制台向标准/错误输出写入新行时,都会触发 ConsoleOutput 事件。这些行被排队并保证按照输出顺序进行。

也可作为 NuGet 包 提供。

获取完整控制台输出的示例调用:

// Run simplest shell command and return its output.
public static string GetWindowsVersion()
{
    return ConsoleApp.Run("cmd", "/c ver").Output.Trim();
}

带实时反馈的示例:

// Run ping.exe asynchronously and return roundtrip times back to the caller in a callback
public static void PingUrl(string url, Action<string> replyHandler)
{
    var regex = new Regex("(time=|Average = )(?<time>.*?ms)", RegexOptions.Compiled);
    var app = new ConsoleApp("ping", url);
    app.ConsoleOutput += (o, args) =>
    {
        var match = regex.Match(args.Line);
        if (match.Success)
        {
            var roundtripTime = match.Groups["time"].Value;
            replyHandler(roundtripTime);
        }
    };
    app.Run();
}

2
我已经在O2平台(开源项目)中添加了许多帮助方法,使您可以轻松地通过控制台输出和输入脚本与另一个进程交互(请参见http://code.google.com/p/o2platform/source/browse/trunk/O2_Scripts/APIs/Windows/CmdExe/CmdExeAPI.cs)。
此外,对您有用的可能还包括允许查看当前进程的控制台输出的API(在现有控件或弹出窗口中)。有关更多详细信息,请参见此博客文章:http://o2platform.wordpress.com/2011/11/26/api_consoleout-cs-inprocess-capture-of-the-console-output/ (此博客还包含有关如何使用新进程的控制台输出的详细信息)。

自那时起,我已经增加了更多支持使用ConsoleOut(在这种情况下,如果您自己启动.NET进程)。看一下: 如何在C# REPL中使用控制台输出将“控制台输出”添加到VisualStudio IDE作为本机窗口查看在UserControls内创建的“控制台输出”消息 - Dinis Cruz

2

我制作了一个响应式版本,它接受stdOut和StdErr的回调函数。
onStdOutonStdErr 在数据到达时(进程退出之前)异步调用。

public static Int32 RunProcess(String path,
                               String args,
                       Action<String> onStdOut = null,
                       Action<String> onStdErr = null)
    {
        var readStdOut = onStdOut != null;
        var readStdErr = onStdErr != null;

        var process = new Process
        {
            StartInfo =
            {
                FileName = path,
                Arguments = args,
                CreateNoWindow = true,
                UseShellExecute = false,
                RedirectStandardOutput = readStdOut,
                RedirectStandardError = readStdErr,
            }
        };

        process.Start();

        if (readStdOut) Task.Run(() => ReadStream(process.StandardOutput, onStdOut));
        if (readStdErr) Task.Run(() => ReadStream(process.StandardError, onStdErr));

        process.WaitForExit();

        return process.ExitCode;
    }

    private static void ReadStream(TextReader textReader, Action<String> callback)
    {
        while (true)
        {
            var line = textReader.ReadLine();
            if (line == null)
                break;

            callback(line);
        }
    }

使用示例

以下代码将运行带有参数argsexecutable并将输出打印到控制台:

  • 标准输出使用白色显示
  • 标准错误使用红色显示
RunProcess(
    executable,
    args,
    s => { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(s); },
    s => { Console.ForegroundColor = ConsoleColor.Red;   Console.WriteLine(s); } 
);

1

来自PythonTR - Python Programcıları Derneği, e-kitap, örnek

Process p = new Process();   // Create new object
p.StartInfo.UseShellExecute = false;  // Do not use shell
p.StartInfo.RedirectStandardOutput = true;   // Redirect output
p.StartInfo.FileName = "c:\\python26\\python.exe";   // Path of our Python compiler
p.StartInfo.Arguments = "c:\\python26\\Hello_C_Python.py";   // Path of the .py to be executed

1

添加了process.StartInfo.**CreateNoWindow** = true;timeout

private static void CaptureConsoleAppOutput(string exeName, string arguments, int timeoutMilliseconds, out int exitCode, out string output)
{
    using (Process process = new Process())
    {
        process.StartInfo.FileName = exeName;
        process.StartInfo.Arguments = arguments;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.CreateNoWindow = true;
        process.Start();

        output = process.StandardOutput.ReadToEnd();

        bool exited = process.WaitForExit(timeoutMilliseconds);
        if (exited)
        {
            exitCode = process.ExitCode;
        }
        else
        {
            exitCode = -1;
        }
    }
}

1
当您使用StandardOutput.ReadToEnd()时,它不会返回到下一个语句,直到应用程序结束。因此,您在WaitForExit(timeoutMilliseconds)中设置的超时时间不起作用!(您的代码将挂起!) - S.Serpooshan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接