在单个控制台行上处理输出重定向

本文关键字:处理 输出 重定向 单个 控制台 | 更新日期: 2023-09-27 18:22:06

我正在编写一个C#应用程序,它可以启动第三方命令行可执行文件,并将其标准输出重定向到RichTextBox。

我能够通过使用该进程重定向输出。OutputDataReceived事件。我有一个将行推送到的队列,然后我使用计时器定期将所有排队的行转储到RichTextBox中。

这非常适用于某些第三方程序。

问题在于程序多次写入控制台的单行。例如:5%->10%->25%等(不断重写同一点)我想,这是通过不放换行符来完成的,而是简单地返回到带有回车符的行的开头,并重写前面的字符。

结果是OutputDataReceived似乎没有任何方式来指示是否应该应用新行,所以我的RichTextBox输出只是在新行上有多个更新。例如:5%10%25%等等。

有什么方法可以判断标准输出是在新行还是在同一行?

在单个控制台行上处理输出重定向

如果我理解正确,您当前看到的进度更新报告给RichTextBox控件是单独的行,而您希望模拟控制台窗口的行为。即用新的输出行覆盖以前的输出行,而不是保留前一行并添加一个新的。考虑到这一点…

.NET类型,包括TextReaderProcess类对输出的处理,将换行视为'r'n'r'n中的任何一个。因此,即使是简单地使用'r来覆盖当前行的过程,OutputDataReceived仍然会将其解释为开始新的行。考虑到这种行为在.NET中的一致性,您唯一真正的选择就是避免使用任何内置的基于行的I/O机制。

幸运的是,您可以直接通过StandardOutput从进程中获得异步读取,而不必处理基于行的OutputDataReceived事件。例如:

async Task ConsumeOutput(TextReader reader, Action<string> callback)
{
    char[] buffer = new char[256];
    int cch;
    while ((cch = await reader.ReadAsync(buffer, 0, buffer.Length)) > 0)
    {
        callback(new string(buffer, 0, cch));
    }
}

你可以这样称呼它:

// don't "await", but do keep "task" so that at some later time, you can
// check the result, "await" it, whatever, to determine the outcome.
Task task = ConsumeOutput(process.StandardOutput, s =>
    {
        richTextBox1.AppendText(s);
    });

当然,在没有任何修改的情况下,上面的内容只会做你已经得到的。但是以这种方式读取输出不会对换行字符进行任何解释。相反,它们将出现在传递给处理每个读取回调的匿名方法的字符串s中。因此,您可以自己扫描该字符串,寻找单独的回车符,该回车符表示您正在开始新行,但应在添加新文本之前删除前一行。请确保先将所有文本添加(或至少跳过)到回车,然后删除最后一行,再添加新文本。

此代码将修复原始输出并使其与终端上显示的一样。

 //Deletes all lines that end with only a CR - this emulates the output as it would be seen cmd
public static string DeleteCRLines( this string Str) {
    Str = Str.Replace( "'r'r'n", "'r'n");
    //Splits at the CRLF. We will end up with 'lines' that are full of CR - the last CR-seperated-line is the one we keep
    var AllLines = new List<string>( Str.Split( new[] {"'r'n"}, StringSplitOptions.None));
    for (int i = 0; i < AllLines.Count; i++){
        var CRLines = AllLines[i].Split(''r');
        AllLines[i] = CRLines[ CRLines.Count() -1];
    }
    return String.Join( Environment.NewLine, AllLines.ToArray());
}