重定向控制台应用程序的标准输入

16

我有一个控制台应用程序,我正在尝试通过重定向进程的标准输入流来自动化它。在手动模式下打开应用程序后,它会像下面这样等待用户输入:enter image description here

我使用了重定向标准输入流的方式创建了该进程。代码片段如下:

Process newProcess = new Process();
newProcess.StartInfo.FileName = exeName;
newProcess.StartInfo.Arguments = argsLine;
newProcess.StartInfo.UseShellExecute = false;
newProcess.StartInfo.RedirectStandardOutput = false ;
newProcess.StartInfo.CreateNoWindow = false;
newProcess.StartInfo.RedirectStandardInput = true;
newProcess.Start();

然而,像这样创建的进程会导致下面的无限循环, enter image description here

就好像我一直在向进程输入流发送Enter键命令。有人可以指出我在这里做错了什么吗?

同样地,在进行了如下更改后,标准输出流重定向也不起作用:

newProcess.StartInfo.RedirectStandardOutput = true

但我可以处理那个。

标准流的重定向是否适用于所有控制台应用程序,还是有任何例外情况?


1
循环中的代码是什么样子? - Matthew Watson
@Watson,我没有控制台应用程序的代码。它是第三方工具。 - Vignesh
2
请不要在问题标题中包含有关所使用编程语言的信息,除非没有它就没有意义。标签可以起到这个作用。 - Ondrej Janacek
3个回答

26

这是我编写的一个类,用于处理那种事情。请随意使用它。它的目的是启动控制台应用程序并与其“交互”。它也可以接收输出。祝好运。

using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

public class ConsoleAppManager
{
    private readonly string appName;
    private readonly Process process = new Process();
    private readonly object theLock = new object();
    private SynchronizationContext context;
    private string pendingWriteData;

    public ConsoleAppManager(string appName)
    {
        this.appName = appName;

        this.process.StartInfo.FileName = this.appName;
        this.process.StartInfo.RedirectStandardError = true;
        this.process.StartInfo.StandardErrorEncoding = Encoding.UTF8;

        this.process.StartInfo.RedirectStandardInput = true;
        this.process.StartInfo.RedirectStandardOutput = true;
        this.process.EnableRaisingEvents = true;
        this.process.StartInfo.CreateNoWindow = true;

        this.process.StartInfo.UseShellExecute = false;

        this.process.StartInfo.StandardOutputEncoding = Encoding.UTF8;

        this.process.Exited += this.ProcessOnExited;
    }

    public event EventHandler<string> ErrorTextReceived;
    public event EventHandler ProcessExited;
    public event EventHandler<string> StandartTextReceived;

    public int ExitCode
    {
        get { return this.process.ExitCode; }
    }

    public bool Running
    {
        get; private set;
    }

    public void ExecuteAsync(params string[] args)
    {
        if (this.Running)
        {
            throw new InvalidOperationException(
                "Process is still Running. Please wait for the process to complete.");
        }

        string arguments = string.Join(" ", args);

        this.process.StartInfo.Arguments = arguments;

        this.context = SynchronizationContext.Current;

        this.process.Start();
        this.Running = true;

        new Task(this.ReadOutputAsync).Start();
        new Task(this.WriteInputTask).Start();
        new Task(this.ReadOutputErrorAsync).Start();
    }

    public void Write(string data)
    {
        if (data == null)
        {
            return;
        }

        lock (this.theLock)
        {
            this.pendingWriteData = data;
        }
    }

    public void WriteLine(string data)
    {
        this.Write(data + Environment.NewLine);
    }

    protected virtual void OnErrorTextReceived(string e)
    {
        EventHandler<string> handler = this.ErrorTextReceived;

        if (handler != null)
        {
            if (this.context != null)
            {
                this.context.Post(delegate { handler(this, e); }, null);
            }
            else
            {
                handler(this, e);
            }
        }
    }

    protected virtual void OnProcessExited()
    {
        EventHandler handler = this.ProcessExited;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    protected virtual void OnStandartTextReceived(string e)
    {
        EventHandler<string> handler = this.StandartTextReceived;

        if (handler != null)
        {
            if (this.context != null)
            {
                this.context.Post(delegate { handler(this, e); }, null);
            }
            else
            {
                handler(this, e);
            }
        }
    }

    private void ProcessOnExited(object sender, EventArgs eventArgs)
    {
        this.OnProcessExited();
    }

    private async void ReadOutputAsync()
    {
        var standart = new StringBuilder();
        var buff = new char[1024];
        int length;

        while (this.process.HasExited == false)
        {
            standart.Clear();

            length = await this.process.StandardOutput.ReadAsync(buff, 0, buff.Length);
            standart.Append(buff.SubArray(0, length));
            this.OnStandartTextReceived(standart.ToString());
            Thread.Sleep(1);
        }

        this.Running = false;
    }

    private async void ReadOutputErrorAsync()
    {
        var sb = new StringBuilder();

        do
        {
            sb.Clear();
            var buff = new char[1024];
            int length = await this.process.StandardError.ReadAsync(buff, 0, buff.Length);
            sb.Append(buff.SubArray(0, length));
            this.OnErrorTextReceived(sb.ToString());
            Thread.Sleep(1);
        }
        while (this.process.HasExited == false);
    }

    private async void WriteInputTask()
    {
        while (this.process.HasExited == false)
        {
            Thread.Sleep(1);

            if (this.pendingWriteData != null)
            {
                await this.process.StandardInput.WriteLineAsync(this.pendingWriteData);
                await this.process.StandardInput.FlushAsync();

                lock (this.theLock)
                {
                    this.pendingWriteData = null;
                }
            }
        }
    }
}

谢谢提供代码。由于我正在使用VS2010,无法使用async和await。:( 我尝试编辑和删除所有的async部分后运行代码(不确定是否正确),但我仍然遇到了我在问题中解释过的相同问题。 - Vignesh
5
你确定答案必须包含像区域和显然的XML注释这样的许多噪音吗? - abatishchev
抱歉,这是从一个被StyleCop格式化的文件中复制粘贴的。 - Mihail Shishkov
我尝试使用以下代码:var cam = new ConsoleAppManager("sudo"); cam.ExecuteAsync("apt install vim"); while (cam.Running) Thread.Sleep(1000);,但是它没有起作用(挂起了),我是否漏掉了什么? - knocte
@Mihail Shishkov,这段代码非常有用。请问您能否解释一下如何将此代码重写为.NET 3.5(不使用Threading.Tasks类)? - neo
1
@neo 好的,我会在明天有时间的时候添加3.5版本。 - Mihail Shishkov

9

接下来是之前的回答,我会添加SubArray的扩展方法,以防万一将此类添加到您的代码中,不要嵌套在任何类中(在评论中代码看起来不可读,所以我在这里添加了它)

public static class CharArrayExtensions
{
    public static char[] SubArray(this char[] input,int startIndex, int length)
    {
        List<char> result= new List<char>();
        for (int i = startIndex; i < length; i++)
        {
            result.Add(input[i]);
        }

        return result.ToArray();
    }
}

0
感谢ConsoleAppManager,它运行良好,但我必须提到,使用Process类中的事件:OutputDataReceivedErrorDataReceived,并在启动进程后调用BeginOutputReadLine()BeginErrorReadLine()可以实现捕获输出和错误消息的相同效果。

此外,WriteInputTask()会导致CPU占用过高,可能需要在while循环和设置pendingWriteData之间使用某种类型的ResetEvent或直接将输入发送到进程中。


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