在单个控制台行上进行进程输出重定向

4

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

通过使用process.OutputDataReceived事件,我能够成功地重定向输出。我有一个队列,将每一行推入队列中,然后我使用计时器定期将所有排队的行转储到RichTextBox中。

这对于一些第三方程序来说运行得很完美。

问题出现在写入单个控制台行多次的程序上。例如: 5% -> 10% -> 25%等...(不断覆盖相同的位置) 我想这是通过不使用换行符,而是简单地返回到行的开头并覆盖先前的字符来完成的。

问题是OutputDataReceived似乎没有任何方法指示是否应该应用新行,因此我的RichTextBox输出只是在新行上进行多次更新。 例如: 5% 10% 25% 等等...

有没有办法判断标准输出是进入新行还是同一行?


你是以二进制模式打开输入文件吗?如果不是,那么可能需要这样做,否则你会看到控制字符。 - Hogan
哦,有趣...我一直在使用事件中的DataReceivedEventArgs来获取字符串。 我没有意识到你可以用process.StandardOutput打开一个流...我需要进行实验。 - Corey
Process.StandardOutput 不是一个流本身,而是一个 TextReader,它从流中解码数据。但是,是的,你可以直接从中读取;请参见我下面发布的答案。 - Peter Duniho
2个回答

5
如果我理解正确,您目前正在将进度更新报告到您的RichTextBox控件中作为单独的行,而您希望模拟控制台窗口的行为。也就是说,覆盖先前输出的行以新行代替,而不是保留上一行并添加一个新行。考虑到这一点...
.NET类型(包括像TextReaderProcess类对输出的处理)将换行符视为任何 \r \n \r\n 。因此,即使是仅使用 \r 来覆盖当前行的进程,在OutputDataReceived中仍会被解释为开始新行。鉴于.NET在这方面的一致行为,你唯一的选择是避免使用任何内置的基于行的I/O机制。
幸运的是,您可以直接从进程中获取异步读取,而不必处理基于行的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);
    });

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

1
谢谢,这段代码完美地运行了,但是要实际获得正确的输出需要相当多的额外代码来配合它。因为字符以长流的形式出现,你不能假设一行会以\r\n或\r结束,因此你需要逐个字符地处理并确定是否要换行。这也可能会有用:https://dev59.com/o4nca4cB1Zd3GeqP-F9f - Corey
这种方法在处理过程的响应不总是基于行的情况下也非常有用。我的处理过程返回任意数量的数据行(每行带有一个换行符),然后是一个提示符(没有换行符)。我需要捕获“提示符”以知道何时可以发送下一个数据请求。 - quilkin

1
这段代码将修复原始输出,并使其与终端上显示的内容相同。
 //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());
}

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