交互式c#System.Process不回显输入

时间:2015-03-24 18:59:46

标签: c# linux shell ssh mono

鉴于以下代码在Linux上运行Mono,我可以从C#成功运行ssh并在远程盒子上获得shell提示符。我可以输入命令并获得输出。但是,我无法弄清楚如何将我输入的内容反馈回来。当我输入ls然后点击输入时,您没有看到ls或新线来点击回车键,您只能看到它的输出。我已经验证了ssh正在分配一个tty。目标shell是以交互模式bash,因此在那里启用了readline。问题必须在于C#如何将STDIN和STDOUT连接到Console。谷歌没有帮助,所以我希望这里有人可以提供帮助。

var process_info = new ProcessStartInfo("/usr/bin/ssh");
process_info.Arguments =  "-ttt hostname";
Console.Out.WriteLine("Arguments: [" + process_info.Arguments + "]");
process_info.CreateNoWindow = true;
process_info.UseShellExecute = true;
var process = new Process();
process.StartInfo = process_info;
try {
    process.Start();
    process.WaitForExit();
    exitCode = process.ExitCode;
}
catch (Exception e)
{
    exitCode = this.ExitCode == 0 ? 255 : exitCode;
    Console.WriteLine(e.ToString());
}
Console.Out.WriteLine("ExitCode: " + exitCode);

3 个答案:

答案 0 :(得分:1)

也许这就是你想要做的事情:

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace Echo
{
    class Program
    {
        private static void Read(StreamReader reader)
        {
            new Thread(() =>
            {
                while (true)
                {
                    int current;
                    while ((current = reader.Read()) >= 0)
                        Console.Write((char)current);
                }
            }).Start();
        }

        static void Main(string[] args)
        {
            ProcessStartInfo startInfo = new ProcessStartInfo(@"/usr/bin/ssh");
            startInfo.Arguments = "-ttty localhost";
            startInfo.CreateNoWindow = true;
            startInfo.ErrorDialog = false;
            startInfo.RedirectStandardError = true;
            startInfo.RedirectStandardInput = true;
            startInfo.RedirectStandardOutput = true;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true;
            Process process = new Process();
            process.StartInfo = startInfo;
            process.Start();
            Thread.Sleep(15000); //time to login
            Read(process.StandardOutput);
            Read(process.StandardError);
            process.StandardInput.WriteLine("echoing your input now");
            while (!process.HasExited)
                try { process.StandardInput.WriteLine(Console.ReadLine()); }
                catch {}
            Console.WriteLine(process.ExitCode.ToString());    
        }
    }
}

编辑1

你需要重定向StandardInput才能回显它,但是windows中的cmd会逐行详细说明(即使你使用Console.ReadKey()=> process.StandardInput.Write),所以你可以&键入时有shell支持(如果你想深入了解,请查看此question/answer)。 但是带有linux ssh的mono与windows cmd的行为不同,所以可能接受以下内容: 命令被回显,甚至键入dir的标签也被管理(请看下面的截图)!最后请注意tty设置正确。

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;

namespace Echo
{
    class Program
    {
        private static Process process;
        private static void Read(StreamReader reader)
        {
            new Thread(() =>
            {
                while (!process.HasExited)
                {
                    int current;
                    while ((current = reader.Read()) >= 0)
                        Console.Write((char)current);
                }
            }).Start();

       }

        static void Main(string[] args)
        {
            ProcessStartInfo startInfo = new ProcessStartInfo(@"/usr/bin/ssh");
            startInfo.Arguments = "-ttty localhost";
            startInfo.CreateNoWindow = true;
            startInfo.ErrorDialog = false;
            startInfo.RedirectStandardError = true;
            startInfo.RedirectStandardInput = true;
            startInfo.RedirectStandardOutput = true;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true;
            process = new Process();
            process.StartInfo = startInfo;
            process.Start();
            Thread.Sleep(15000); //time to login
            Read(process.StandardOutput);
            Read(process.StandardError);
            process.StandardInput.WriteLine("echo echoing your input now");
            //Console.ReadLine();
            string theLine = "\n";
            while (!process.HasExited)
                try {
                    ConsoleKeyInfo kinfo =  Console.ReadKey(true);
                   char theKey = kinfo.KeyChar;
                    theLine += theKey;
                    process.StandardInput.Write(theKey) ;
                    process.StandardInput.Flush();
                    if (theKey.Equals('\n'))
                    {
                        Console.WriteLine(theLine);
                        theLine = "\n";
                    }

                }
                catch { }
            Console.WriteLine(process.ExitCode.ToString());
        }
    }
}

first example of using tab while typing a dir check tty example of echoing the ssh commmands

编辑2

如果你还想管理UpArrow / DownArrow的终端转义序列,这里是代码(在我的Ubuntu终端上测试)

            string theLine = "\n";
            string theEsc = ((char)27).ToString();
            while (!process.HasExited)
                try {
                    //byte[] bytes = new byte[1];
                    ConsoleKeyInfo kinfo =  Console.ReadKey(true);
                    char theKey = kinfo.KeyChar;
                    theLine += theKey;
                    switch (kinfo.Key)
        {

case ConsoleKey.DownArrow:
                            process.StandardInput.Write(theEsc+"[B");
 break;
case ConsoleKey.UpArrow:
                            process.StandardInput.Write(theEsc+"[A");
 break;
default:
                            process.StandardInput.Write(theKey);
 break;
        }
                    process.StandardInput.Flush();
                    if (theKey.Equals('\n'))
                    {
                        Console.Write(theLine);
                        theLine = "\n";

                    }

                }

编辑3

只需跟进我的评论,建议恢复回声(参考here)。 这是对代码的更改:

        process.StandardInput.WriteLine("stty -a");
        process.StandardInput.WriteLine("stty echo"); // or "reset" insted of "stty echo"
        process.StandardInput.WriteLine("echo echoing your input now");

返回原始代码(因为您没有重定向标准输入),您可以执行以下操作

process_info.Arguments =  "-ttt hostname 'stty echo; '$SHELL' -i'"; // or reset insted of stty echo

请看这个answer

总之 来源您正在展示 - 更具体地说是c#System.Process - 应该 echo 任何东西(除非有意重定向标准I / O,正如我在第一个例子和编辑1& 2中​​所做的那样)。   Echoing 是一种shell的行为,在Linux和Windows中:可以如编辑3所示进行管理。

答案 1 :(得分:1)

我偶然发现了同样的问题,对用户4569980的分析帮助很大。

此行为的根本原因是mono禁用当前使用的tty的echo功能。 请参阅http://www.linusakesson.net/programming/tty/https://github.com/mono/mono/blob/master/mcs/class/corlib/System/TermInfoDriver.cs#L204

我使用了以下解决方法:

// mono sets echo off for some reason, therefore interactive mode // doesn't work as expected this enables this tty feature which // makes the interactive mode work as expected let private setEcho (b:bool) = // See https://github.com/mono/mono/blob/master/mcs/class/corlib/System/ConsoleDriver.cs#L289 let t = System.Type.GetType("System.ConsoleDriver") if Env.isMono then let flags = System.Reflection.BindingFlags.Static ||| System.Reflection.BindingFlags.NonPublic if isNull t then eprintfn "Expected to find System.ConsoleDriver.SetEcho" false else let setEchoMethod = t.GetMethod("SetEcho", flags) if isNull setEchoMethod then eprintfn "Expected to find System.ConsoleDriver.SetEcho" false else setEchoMethod.Invoke(null, [| b :> obj |]) :?> bool else false

我将它留给感兴趣的读者将此F#代码转换为C#。它基本上是对bool System.ConsoleDriver.SetEcho(bool enable)的简单反映。

现在使用以下伪代码:

setEcho(true) var p = startProcess () p.WaitForExit() setEcho(false)

答案 2 :(得分:0)

https://msdn.microsoft.com/de-de/library/system.diagnostics.processstartinfo.redirectstandardinput%28v=vs.110%29.aspx

只需捕获并复制STDIN。

process.Start();
StreamWriter processInputStream = process.StandardInput;
do {
    String inputText = Console.ReadLine():
    processInputStream.write(inputText);
while(!process.HasExited)
process.WaitForExit();

现在SSH进程不再捕获您的输入,因此它应该在本地显示。如果没有,只需将Console.writeLine(inputText)添加到循环中,然后完成。

如果您想要更好的控制,请考虑逐字节读写。请注意,TAB和其他控制字符可能不那么容易处理。

如果您确实需要这些,请改用ReadKey()并传递您需要的任何控制字符。请务必设置Console.TreatControlCAsInput = true;,否则您将无法在不中断申请的情况下发送CMD + c

哦,但是在Windows之外从.NET发送控制序列(带有CMD或ALT修饰符的键)? 呃......我认为其中一个实际上是有限的。这是System.Window.Forms的一部分,我不知道如何使用Windows外的纯C#复制该行为。

至于另一个,可能更容易的选择:
只是不要调用裸ssh可执行文件。改为打开shell并在该内容中运行SSH。 /usr/bin/bash"-c 'ssh -ttt hostname'"。你的问题是TTY仿真,所以让shell为你处理。 Console.TreatControlCAsInput = true;仍然适用,至少如果您希望能够通过Ctrl + C作为命令序列。