在C#中发送命令到命令提示符

11
针对我的一个实现,我正在开发一个工具,旨在发送/检索命令/结果到/从cmd窗口。一切都正常,但下面的用例无法执行任何操作。似乎我的应用程序在等待某些东西(而不是显示结果)。
从我的工具中,我导航到Python文件夹。从Python文件夹中,我尝试启动python.exe,但此时,我的编辑器不会有任何反应。它只是继续等待。
我也提供了视频链接,希望你们能更好地理解我的问题。 在这里观看视频(YouTube) 我还附上了我目前拥有的代码。
            ProcessStartInfo info = new ProcessStartInfo("cmd.exe");

            string argument = null;
            if (!string.IsNullOrEmpty(startingDirectory) && System.IO.Directory.Exists(startingDirectory))
            {
               argument += @"cd\";
            }
            else
            {
                argument += "\"";
            }
            info.Arguments = argument;
            info.CreateNoWindow = true;
            info.RedirectStandardError = true;
            info.RedirectStandardInput = true;
            info.RedirectStandardOutput = true;
            info.UseShellExecute = false;
             this.shellProcess = System.Diagnostics.Process.Start(info);
            this.shellProcess.EnableRaisingEvents = true;
            //this.InputStream.AutoFlush = true;
            this.shellProcess.Exited += new EventHandler(ProcessExited);
            this.ErrorBeginRead();
            this.OutputBeginRead();

 private void OutputBeginRead()
    {
        this.shellProcess.StandardOutput.BaseStream.BeginRead(outputBuffer, 0, outputBuffer.Length, new AsyncCallback(this.OnOutputInput), this.shellProcess);
    }

        private void ErrorBeginRead()
    {
        this.shellProcess.StandardError.BaseStream.BeginRead(errorBuffer, 0, errorBuffer.Length, new AsyncCallback(this.OnErrorInput), this.shellProcess);
    }

谢谢!

编辑:启动Python只是一个例子。我需要使用同样的方法来运行其他普通的命令行命令。如果有人能指出我代码中的错误或者我应该做什么才能实现预期的功能,那将会很棒。

编辑2:普通的命令行命令可以正常工作。像Python、Perl这样的命令行工具不能正常工作。

编辑3:所以我按照Jamie的建议稍微前进了一点。界面不再“挂起”了。但当我访问Python解释器时,解释器的输出仍然无法在我的工具中显示。有什么建议为什么会发生这种情况吗?

3个回答

23

这种方式无法发送命令到shell。info.Arguments中的字符串是在命令行中提供给程序的参数。如果您想让cmd.exe shell执行一系列命令,然后退出,您将需要提供/c参数。如果您有多个要执行的命令,您将需要将它们放入批处理文件中并执行该文件,或者用引号括起来并用&&分隔它们,例如:info.Arguments = @"/c ""cd \ && dir""";。您遇到的另一个问题是,当没有任何或正确的参数时,cmd.exe默认以交互模式打开。/c选项告诉cmd.exe执行相关命令,然后退出。

此外,像Python和Perl这样的解释器有时会在直接从ProcessStartInfo启动时出现奇怪的行为。如果使用info.Arguments = @"""MyPerlProgram.pl""";和perl.exe不起作用,则可能需要在cmd.exe中启动它们,以便获得正常的行为,例如:info.Arguments = @"/c ""perl.exe ""MyPerlProgram.pl""""";

请参见CmdProcessStartInfo.Arguments Property

为了回答您的Edit 3问题,您可能没有正确地连接输出。而不是尝试钩住StreamReader的BaseStream,请使用this.shellProcess.OutputDataReceived += ProcessOutputHandler;钩住OutputDataReceived事件,在调用Start之前,ProcessOutputHandler具有类似于public static void ProcessOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)的签名。在调用Start后立即调用this.shellProcess.BeginOutputReadLine();。对于错误输出,处理过程也类似。请参见Process.BeginOutputReadLine MethodProcess.BeginErrorReadLine Method了解更多详细信息。

如果仍然有问题,请尝试以下代码:process.StartInfo.Arguments = @"/c ""python.exe -c ""import sys; print 'Test.';""""";

此外,下面的代码演示了与shell通信的大部分必要概念:

public static void Main()
{
    using (Process process = new Process())
    {
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.WorkingDirectory = @"C:\";
        process.StartInfo.FileName = Path.Combine(Environment.SystemDirectory, "cmd.exe");

        // Redirects the standard input so that commands can be sent to the shell.
        process.StartInfo.RedirectStandardInput = true;
        // Runs the specified command and exits the shell immediately.
        //process.StartInfo.Arguments = @"/c ""dir""";

        process.OutputDataReceived += ProcessOutputDataHandler;
        process.ErrorDataReceived += ProcessErrorDataHandler;

        process.Start();
        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        // Send a directory command and an exit command to the shell
        process.StandardInput.WriteLine("dir");
        process.StandardInput.WriteLine("exit");

        process.WaitForExit();
    }
}

public static void ProcessOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
    Console.WriteLine(outLine.Data);
}

public static void ProcessErrorDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
    Console.WriteLine(outLine.Data);
}

您可能存在线程问题导致出现问题。我已经对此进行了进一步的工作,并使用以下代码使窗体上的文本框更新:

using System;
using System.Diagnostics;
using System.IO;
using System.Timers;

namespace DummyFormsApplication
{
    class ProcessLauncher : IDisposable
    {
        private Form1 form;
        private Process process;
        private bool running;

        public bool InteractiveMode
        {
            get;
            private set;
        }

        public ProcessLauncher(Form1 form)
        {
            this.form = form;

            process = new Process();
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
            process.StartInfo.WorkingDirectory = @"C:\";
            process.StartInfo.FileName = Path.Combine(Environment.SystemDirectory, "cmd.exe");

            // Redirects the standard input so that commands can be sent to the shell.
            process.StartInfo.RedirectStandardInput = true;

            process.OutputDataReceived +=new DataReceivedEventHandler(process_OutputDataReceived);
            process.ErrorDataReceived += new DataReceivedEventHandler(process_ErrorDataReceived);
            process.Exited += new EventHandler(process_Exited);
        }

        public void Start()
        {
            if (running == false)
            {
                running = true;
                InteractiveMode = true;

                // Runs the specified command and exits the shell immediately upon completion.
                process.StartInfo.Arguments = @"/c ""C:\python27\python.exe -i""";

                process.Start();

                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
            }
        }

        public void Start(string scriptFileName)
        {
            if (running == false)
            {
                running = true;
                InteractiveMode = false;

                // Runs the specified command and exits the shell immediately upon completion.
                process.StartInfo.Arguments = string.Format(@"/c ""C:\python27\python.exe ""{0}""""", scriptFileName);
            }
        }

        public void Abort()
        {
            process.Kill();
        }

        public void SendInput(string input)
        {
            process.StandardInput.Write(input);
            process.StandardInput.Flush();
        }

        private void process_OutputDataReceived(object sendingProcess, DataReceivedEventArgs outLine)
        {
            if (outLine.Data != null)
            {
                form.Invoke(form.appendConsoleTextDelegate, new object[] { outLine.Data });
            }
        }

        private void process_ErrorDataReceived(object sendingProcess, DataReceivedEventArgs outLine)
        {
            if (outLine.Data != null)
            {
                form.Invoke(form.appendConsoleTextDelegate, new object[] { outLine.Data });
            }
        }

        private void process_Exited(object sender, EventArgs e)
        {
            running = false;
        }

        public void Dispose()
        {
            if (process != null)
            {
                process.Dispose();
            }
        }
    }
}

我创建了一个表单并添加了一个文本框,在表单中加入了以下代码:

    public delegate void AppendConsoleText(string text);
    public AppendConsoleText appendConsoleTextDelegate;

    private void Form1_Load(object sender, EventArgs e)
    {
        appendConsoleTextDelegate = new AppendConsoleText(textBox1_AppendConsoleText);
        using (ProcessLauncher launcher = new ProcessLauncher(this))
        {
            launcher.Start();

            launcher.SendInput("import sys;\n");
            launcher.SendInput("print \"Test.\";\n");
            launcher.SendInput("exit()\n");
        }
    }

    private void textBox1_AppendConsoleText(string text)
    {
        textBox1.AppendText(string.Format("{0}\r\n", text));
    }

需要注意的一点是,如果Form1_Load事件没有完成,Invoke将会一直挂起直到它完成。如果你在事件中有长时间运行的代码,你需要使用BeginInvoke异步调用,或者定期在长时间运行的代码中调用DoEvents。

编辑

根据您的评论,我已经修改了代码以适应交互式提交。但是存在一个问题。Python提示符(>>>)在StandardError输出中不被显示,也不会回显StandardInput。它也不会结束这一行。这使得检测提示符变得困难,并且由于process_ErrorDataReceived直到进程结束或看到行末才触发,因此导致某些提示字符的输出顺序错误。


更新了带有示例的答案,解释了cmd.exe的参数,并说明了为什么该进程不会自行终止。 - JamieSee
嗨,Jamie,不幸的是这对我没用。很抱歉,但我仍在努力解决这个问题。 - Gagan
我明白了。现在还是和第三次编辑时一样的问题吗?还是有什么不同的地方? - JamieSee
这是哪个版本和发行版的Python? - JamieSee
是的 - 你评论的后半部分正是我想要的。"我确实希望将Python提示符传递给用户,以便他们可以交互地执行代码。" - Gagan
显示剩余10条评论

3

您的问题中提供的代码不足以确定您的应用程序挂起的原因。您的代码中有一些看起来很奇怪的地方。例如,为什么要启动自己的错误和输出读取循环,而不是使用内置在Process类中的循环?就像这样:

var shellProcess = System.Diagnostics.Process.Start(info);
shellProcess.EnableRaisingEvents = true;
shellProcess.Exited += ProcessExited;

shellProcess.OutputDataReceived += ShellProcess_OutputDataReceived;
shellProcess.ErrorDataReceived  += ShellProcess_ErrorDataReceived;
shellProcess.BeginOutputReadLine();
shellProcess.BeginErrorReadLine();

void ShellProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
    // Do Something
}

void ShellProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    // Do Something
}

由于您的错误和输出异步事件未触发,我认为 shellProcess 可能存在生命周期问题。如果您发布更多代码,我们可以提供更好的指导。


2

我无法看到你的全部代码,但是你可以很容易地使用Steam对象向你创建的CMD窗口写入/发送命令。例如:

StreamWriter inputStream = shellProcess.StandardInput;
//send command to cmd prompt and wait for command to execute with thread sleep
inputStream.WriteLine("echo "CMD just received input");
inputStream.Flush();

在上面的例子中,命令提示符将接收echo命令,就像在窗口中输入一样。要显示输出,您需要创建StreamReader对象并将其分配给进程的StandardOutput

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