20

我想以控制台中的形式读取进程的输出(标准输出与一个流中的标准错误混合)。有没有办法做到这一点?

我在考虑使用

ProcessStartInfo.UseShellExecute = true;  

但后来我无法异步读取输出。如果我设置

process.ProcessStartInfo.UseShellExecute = false;  
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += new DataReceivedEventHandler(partialOutputHandler);

然后我可以读取标准输出(我可以对标准错误执行相同的操作)但我不知道如何模拟控制台的行为(stdout 和 stderr 的混合)。

这类似于 Linux 具有将标准错误流重定向到标准输出流的特性;如何?

4

4 回答 4

47

你的意思是这样的吗?

SynchronizationContext _syncContext;
MyForm()
{
    _syncContext = SynchronizationContext.Current;
}

void StartProcess()
{
    using (var process = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "myProcess.exe",
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
            }
        })
    {
        process.OutputDataReceived += (sender, args) => Display(args.Data);
        process.ErrorDataReceived += (sender, args) => Display(args.Data);

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

        process.WaitForExit(); //you need this in order to flush the output buffer
    }   
}

void Display(string output)
{
    _syncContext.Post(_ => myTextBox.AppendText(output), null);
}
于 2012-09-24T18:34:31.393 回答
3

MSDN文章指出:

重定向的 StandardError 流可以同步或异步读取。Read、ReadLine、ReadToEnd等方法对进程的错误输出流进行同步读操作。在关联的 Process 写入其 StandardError 流或关闭流之前,这些同步读取操作不会完成。

相反,BeginErrorReadLine 在 StandardError 流上启动异步读取操作。此方法为流输出启用指定的事件处理程序并立即返回给调用者,调用者可以在流输出被定向到事件处理程序时执行其他工作。

同步读取操作在从 StandardError 流读取的调用者和写入该流的子进程之间引入了依赖关系。这些依赖关系可能导致死锁情况。当调用者从子进程的重定向流中读取时,它依赖于子进程。调用者等待读取操作,直到子进程写入流或关闭流。当子进程写入足够的数据来填充其重定向流时,它依赖于父进程。子进程等待下一次写入操作,直到父进程从完整流中读取或关闭流。当调用者和子进程互相等待完成一个操作,并且两者都不能继续时,就会出现死锁情况。

这同样适用于StandardOutput,因此您只需异步读取两个流。

Merging两个流合二为一使得检测什么输出是错误报告以及什么是“产品”信息变得复杂。

于 2012-09-24T13:46:53.590 回答
3

我找到了答案:

输出流被缓冲。无法获得插入流中的项目的真实顺序。事实上,这没有什么意义,因为两个流也可以同时写入。它们彼此独立。因此,您能做的最好的事情就是在它们到达时从每个输出中获取顺序输出。

通常这不是问题,因为几乎所有控制台应用程序都将标准输出用于输出和错误消息。错误流被某些应用程序使用,但消息通常与输出流中生成的错误重复。

资料来源: http ://social.msdn.microsoft.com/Forums/uk/csharpgeneral/thread/192b6df7-9437-42cf-81c1-c125021735ba

于 2012-10-02T09:40:10.940 回答
1

类似的示例,除了我为此目的使用 StringBuilder 将标准输出和错误收集到单独的字符串中。

/// <summary>
/// Executes command
/// </summary>
/// <param name="cmd">command to be executed</param>
/// <param name="output">output which application produced</param>
/// <param name="transferEnvVars">true - if retain PATH environment variable from executed command</param>
/// <returns>true if process exited with code 0</returns>
static bool ExecCmd(string cmd, out String output, bool transferEnvVars = false)
{
    ProcessStartInfo processInfo;
    Process process;

    if (transferEnvVars)
        cmd =  cmd + " && echo --VARS-- && set";

    processInfo = new ProcessStartInfo("cmd.exe", "/c " + cmd);
    processInfo.CreateNoWindow = true;
    processInfo.UseShellExecute = false;
    processInfo.RedirectStandardError = true;
    processInfo.RedirectStandardOutput = true;

    process = Process.Start(processInfo);

    // Executing long lasting operation in batch file will hang the process, as it will wait standard output / error pipes to be processed.
    // We process these pipes here asynchronously.
    StringBuilder so = new StringBuilder();
    process.OutputDataReceived += (sender, args) => { so.AppendLine(args.Data); };
    StringBuilder se = new StringBuilder();
    process.ErrorDataReceived += (sender, args) => { se.AppendLine(args.Data); };

    process.BeginOutputReadLine();
    process.BeginErrorReadLine();
    process.WaitForExit();

    output = so.ToString();
    String error = se.ToString();

    if (transferEnvVars)
    {
        Regex r = new Regex("--VARS--(.*)", RegexOptions.Singleline);
        var m = r.Match(output);
        if (m.Success)
        {
            output = r.Replace(output, "");

            foreach ( Match m2 in new Regex("(.*?)=([^\r]*)", RegexOptions.Multiline).Matches(m.Groups[1].ToString()) )
            {
                String key = m2.Groups[1].Value;
                String value = m2.Groups[2].Value;
                Environment.SetEnvironmentVariable(key, value);
            }
        }
    }

    if(error.Length != 0)
        output += error;
    int exitCode = process.ExitCode;

    if (exitCode != 0)
        Console.WriteLine("Error: " + output + "\r\n" + error);

    process.Close();
    return exitCode == 0;
}
于 2018-11-16T05:39:33.447 回答