如果我不关闭 StardandInput,C# 重定向其他控制台应用程序 StandardOutput 会失败

2023-12-19

我的控制台应用程序遇到了一个棘手的问题,我试图从中重定向 StandardInput、StandardOutput 和 StandardError。

我已经为其他控制台应用程序找到了一个可行的解决方案 - 这对我来说并不是什么新鲜事。但这个应用程序似乎有一些特殊之处,这使得不可能用标准方法重定向所有输出。

控制台应用程序的工作方式如下:

  • 启动后立即写入几行并等待用户输入
  • 无论输入是什么 - 控制台应用程序都会显示一些输出并再次等待新输入
  • 控制台应用程序永远不会结束,必须关闭

我已经尝试过基于以下内容的解决方案:

  • 标准输出/Error.ReadToEnd()
  • taki 负责使用 ReadLine 逐行读取 OutputDataReceived 和 ErrorDataReceived
  • 按字符读取
  • 等待进程结束(这并没有结束,所以我遇到了死锁)
  • 在预加载的 cmd.exe 中启动控制台应用程序并获取此应用程序(标准输出在启动此控制台应用程序后停止显示)
  • 手动刷新输入

一直以来,我完全没有任何输出,也没有来自控制台应用程序的错误流 - 什么都没有。

经过多次尝试后,我发现,只有在以编程方式输入数据后关闭 StandardInput 时,我才能收到 StandardOutput。

但在这种情况下,控制台应用程序会变得疯狂,陷入启动时向 StandardOutput 写入几行的循环,这使得最终输出很大并且充满垃圾。

使用 MedallionShell 库,我可以尝试使用 Ctrl+C 优雅地杀死它,但这个解决方案仍然远不能接受,因为:

  • 有时控制台应用程序会在我能够杀死它之前产生大量垃圾,从而导致我的应用程序崩溃
  • 即使这不会崩溃,在大量垃圾中搜索预期输出也是令人讨厌的,并且会严重减慢自动化速度(6000 条记录...... 15 分钟)
  • 我无法一次提供多个输入,因此我必须启动控制台应用程序才能接收一个输出,关闭并重新启动以获得另一个输出

我没有该控制台应用程序的来源,所以我什至无法重新创建问题或修复它 - 这是我公司的一些非常遗留的应用程序,我正在尝试使其至少有点自动化......

代码,我现在至少有了一些东西(使用 MediallionShell):

  var command = Command.Run(Environment.CurrentDirectory + @"\console_app.exe");
  command.StandardInput.WriteLine("expected_input");
  command.StandardInput.Close(); // without that I'll never receive any output or error stream from this stubborn console app
  command.TrySignalAsync(CommandSignal.ControlC); // attempt to kill gracefully, not always successfull...
  var result = command.Result;
  textBox1.AppendText(String.Join(Environment.NewLine, command.GetOutputAndErrorLines().ToArray().Take(10))); // trying to get rid of garbages
  command.Kill(); // additional kill attempt if Ctrl+C didn't help, sometimes even this fails

任何帮助将不胜感激,我还在寻找解决方案,现在我正在检查这个:PostMessage 不适用于控制台窗口,其输出被重定向并异步读取 https://stackoverflow.com/questions/14510350/postmessage-not-working-with-console-window-whose-output-is-redirected-and-read但作者有一个输出,而我没有......


您尚未提供用于测试的示例控制台程序,但类似以下内容可能有效:

创建控制台项目 (Console (.NET Framework)) - 用于测试

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleTestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //prompt for input - 1st prompt
            Console.Write("Please enter filename: ");
            string filename = Console.ReadLine();

            if (System.IO.File.Exists(filename))
            {
                Console.WriteLine("'" + filename + "' exists.");
            }
            else
            {
                Console.Error.WriteLine("'" + filename + "' doesn't exist.");
            }

            //prompt for input - 2nd prompt
            Console.Write("Would you like to exit? ");
            string answer = Console.ReadLine();

            Console.WriteLine("Your answer was: " + answer);

            Console.WriteLine("Operation complete.");
        }
    }
}

然后,创建一个 Windows 窗体项目Windows Forms (.NET Framework)并运行以下命令之一:

Option 1:

private void RunCmd(string exePath, string arguments = null)
{
    //create new instance
    ProcessStartInfo startInfo = new ProcessStartInfo(exePath, arguments);
    startInfo.Arguments = arguments; //arguments
    startInfo.CreateNoWindow = true; //don't create a window
    startInfo.RedirectStandardError = true; //redirect standard error
    startInfo.RedirectStandardOutput = true; //redirect standard output
    startInfo.RedirectStandardInput = true;
    startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    startInfo.ErrorDialog = false;

    //create new instance
    using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
    {
        //subscribe to event and add event handler code
        p.ErrorDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code 
                Debug.WriteLine("Error: " + e.Data);
            }
        };

        //subscribe to event and add event handler code
        p.OutputDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code
                Debug.WriteLine("Output: " + e.Data);
            }
        };

        p.Start(); //start

        p.BeginErrorReadLine(); //begin async reading for standard error
        p.BeginOutputReadLine(); //begin async reading for standard output

        using (StreamWriter sw = p.StandardInput)
        {
            //provide values for each input prompt
            //ToDo: add values for each input prompt - changing the for loop as necessary
            for (int i = 0; i < 2; i++)
            {
                if (i == 0)
                    sw.WriteLine(@"C:\Temp\Test1.txt"); //1st prompt
                else if (i == 1)
                    sw.WriteLine("Yes"); //2nd prompt
                else
                    break; //exit
            }
        }

        //waits until the process is finished before continuing
        p.WaitForExit();
    }
}

Option 2:

private void RunCmd(string exePath, string arguments = null)
{
    //create new instance
    ProcessStartInfo startInfo = new ProcessStartInfo(exePath, arguments);
    startInfo.Arguments = arguments; //arguments
    startInfo.CreateNoWindow = true; //don't create a window
    startInfo.RedirectStandardError = true; //redirect standard error
    startInfo.RedirectStandardOutput = true; //redirect standard output
    startInfo.RedirectStandardInput = true;
    startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    startInfo.ErrorDialog = false;

    //create new instance
    using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
    {
        //subscribe to event and add event handler code
        p.ErrorDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code 
                Debug.WriteLine("Error: " + e.Data);
            }
        };

        //subscribe to event and add event handler code
        p.OutputDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code
                Debug.WriteLine("Output: " + e.Data);
            }
        };

        p.Start(); //start

        p.BeginErrorReadLine(); //begin async reading for standard error
        p.BeginOutputReadLine(); //begin async reading for standard output

        using (StreamWriter sw = p.StandardInput)
        {
            //provide values for each input prompt
            //ToDo: add values for each input prompt - changing the for loop as necessary
            sw.WriteLine(@"C:\Temp\Test1.txt"); //1st prompt
            sw.WriteLine("Yes"); //2nd prompt
        }

        //waits until the process is finished before continuing
        p.WaitForExit();

    }
}

Option 3:

Note: 这个是修改自here https://stackoverflow.com/questions/19552012/c-sharp-process-standard-input.

private void RunCmd(string exePath, string arguments = null)
{
    //create new instance
    ProcessStartInfo startInfo = new ProcessStartInfo(exePath, arguments);
    startInfo.Arguments = arguments; //arguments
    startInfo.CreateNoWindow = true; //don't create a window
    startInfo.RedirectStandardError = true; //redirect standard error
    startInfo.RedirectStandardOutput = true; //redirect standard output
    startInfo.RedirectStandardInput = true;
    startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    startInfo.ErrorDialog = false;

    //create new instance
    using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
    {
        p.Start(); //start

        Read(p.StandardOutput);
        Read(p.StandardError);

        using (StreamWriter sw = p.StandardInput)
        {
            //provide values for each input prompt
            //ToDo: add values for each input prompt - changing the for loop as necessary
            sw.WriteLine(@"C:\Temp\Test1.txt"); //1st prompt
            sw.WriteLine("Yes"); //2nd prompt
        }

        //waits until the process is finished before continuing
        p.WaitForExit();

    }
}

private static void Read(StreamReader reader)
{
    new System.Threading.Thread(() =>
    {
        while (true)
        {
            int current;
            while ((current = reader.Read()) >= 0)
                Console.Write((char)current);
        }
    }).Start();
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如果我不关闭 StardandInput,C# 重定向其他控制台应用程序 StandardOutput 会失败 的相关文章

随机推荐