在 C# 中为命令提示符创建实时输入和输出流

Posted

技术标签:

【中文标题】在 C# 中为命令提示符创建实时输入和输出流【英文标题】:Creating Real-Time Input & Output Stream To Command Prompt in C# 【发布时间】:2021-07-28 03:34:55 【问题描述】:

一般问题

如何使用某种形式的 C# 应用程序(无论是 WinForms、WPF 还是 UWP)创建与命令提示符 (cmd.exe) 实例之间的直接输入/输出流。像使用编译库一样有效地使用 CMD。

我真正需要的

我需要能够像直接使用应用程序一样读取和写入命令提示符实例,这意味着可行的解决方案需要能够:

以编程方式将输入作为命令写入 CMD 进程的实例(能够使用 C# 将 ping google.com 传递给 CMD) 在生成时以编程方式读取 CMD 进程的输出(在 ping google.com 的情况下,我应该能够在生成时将每个单独的 ping 作为它自己的行来获取。换句话说:
Pinging google.com [172.217.6.14] with 32bytes of data:
Reply from 172.217.6.14: bytes=32 time=31ms TTL=117
Reply from 172.217.6.14: bytes=32 time=29ms TTL=117
Reply from 172.217.6.14: bytes=32 time=46ms TTL=117
Reply from 172.217.6.14: bytes=32 time=26ms TTL=117
Ping statistics for 172.217.6.14:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 26ms, Maximum = 35ms, Average = 29ms

这些代码块中的每一个都应该能够在生成时被单独引用,无论这意味着将它们写入文本框还是将它们输出到控制台逐行。

我尝试了什么

我已经看过这些帖子,它们提供的答案与我需要的相似,但不是确切的答案:StandardOutput.ReadToEnd() hangs [duplicate]、What's the right c# input stream for cmd.exe encoding、Capturing console output from a .NET application (C#) 和 Reading from a process, StreamReader.Peek() not working as expected

为了简短起见,此示例适用于控制台应用程序 (.NET Framework)。

我尝试使用StreamReader.Peek() 方法创建一个简单的while 循环,以查看是否已到达流的末尾,尽管这有其自身的问题(即它不会动态更新,所以一旦CMDOutput.Peek() 到达-1 它不会恢复,即使从那时起更新了流)。我用于此解决方案的代码示例如下:

ProcessStartInfo CMDStartInfo = new ProcessStartInfo(@"C:\Windows\System32\cmd.exe", "/c ping google.com");

CMDStartInfo.UseShellExecute = false;
CMDStartInfo.ErrorDialog = false;

CMDStartInfo.RedirectStandardError = true;
CMDStartInfo.RedirectStandardInput = true;
CMDStartInfo.RedirectStandardOutput = true;

Process CMD = new Process();
CMD.StartInfo = CMDStartInfo;
bool CMDStarted = CMD.Start();

StreamWriter CMDInput = CMD.StandardInput;
StreamReader CMDOutput = CMD.StandardOutput;
StreamReader CMDErrors = CMD.StandardError;

while (true)

    while (CMDOutput.Peek() > -1)
    
        try
        
            Console.WriteLine(CMDOutput.ReadLine());

         
        catch
        
            return;
        
    

    Console.WriteLine(CMDOutput.Peek());
    CMDInput.WriteLine(Console.ReadLine());


再次,您可以看到此解决方案的主要问题是在输出的第一行之后(Pinging google.com [172.217.6.14] with 32bytes of data:CMDOutput.Peek() 无限期返回-1,并且无法读取更多输出。

我尝试使用不同的方法,通过删除循环并将起始参数更改为 /K,并使用 StreamReader.ReadToEnd() 代替 StreamReader.ReadLine() 哪个有效?我现在可以读取多行,但是它只运行一次,并且在使用 ping 命令时它根本不会输出。更不用说应用程序似乎还挂在最后一行。

旁注:我尝试使用这种方法获得的唯一多行输出来自无效命令。

Process CMD = new Process();
CMD.StartInfo.FileName = "cmd.exe";
CMD.StartInfo.Arguments = "/K";
CMD.StartInfo.UseShellExecute = false;
CMD.StartInfo.RedirectStandardInput = true;
CMD.StartInfo.RedirectStandardOutput = true;
CMD.StartInfo.WorkingDirectory = @"C:\";
CMD.Start();

StreamWriter CMDInput = CMD.StandardInput;
StreamReader CMDOutput = CMD.StandardOutput;

string InputString = Console.ReadLine();

CMDInput.WriteLine(InputString);

string OutputString = CMDOutput.ReadToEnd();

Console.WriteLine(OutputString);

在搜索和试验了很多年之后,我只是不确定从这里去哪里。

解决方案【柠檬天空】:

感谢 Lemon Sky 的快速而简单的回复。对于其他有我的问题的人,我用来解决它的代码可以在下面找到。显然,我需要的确切事件已经存在一个事件侦听器,正如 Lemon Sky 所说,我所要做的就是订阅它 (Process.OutputDataReceived)。如果没有任何过滤,您仍然会收到带有命令的行,因此作为一个简单的修复,我添加了一些过滤来检测输出的行是否以 C:\ 开头。

Process CMD = new Process();
CMD.StartInfo.FileName = "cmd.exe";
CMD.StartInfo.Arguments = "/K";
CMD.StartInfo.UseShellExecute = false;
CMD.StartInfo.RedirectStandardInput = true;
CMD.StartInfo.RedirectStandardOutput = true;
CMD.StartInfo.WorkingDirectory = @"C:\";
CMD.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>


    if (!String.IsNullOrEmpty(e.Data))
    
        if (e.Data.StartsWith(@"C:\"))
        
            return;
        
        else
        
            Console.WriteLine(e.Data);
        
    
);
CMD.Start();

CMD.BeginOutputReadLine();

StreamWriter CMDInput = CMD.StandardInput;

while (true && !CMD.HasExited)

    string InputString = Console.ReadLine();
    if (InputString == "cls" || InputString == "clear")
        Console.Clear();
    else
        CMDInput.WriteLine(InputString);

再次感谢柠檬天空!

【问题讨论】:

【参考方案1】:

设置process.StartInfo.RedirectStandardOutput = true,订阅process.OutputDataReceived,拨打process.BeginOutputReadLine。这适用于由换行符分隔的基于文本的输出。

【讨论】:

以上是关于在 C# 中为命令提示符创建实时输入和输出流的主要内容,如果未能解决你的问题,请参考以下文章

了解下C# 文件的输入与输出

C#中,运行程序时提示:无法直接启动还有类库输出类型的项目?

python如何实时获取命令行执行的输出

c#在以管理员权限运行控制台命令时,怎么不让它弹出uac提示框?

如何将 ctrl+c 发送到 c# 中的进程?

如何查看windows系统当前时间