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

Interactive c# System.Process not Echoing input

在 Linux 上的 Mono 中给出以下代码 运行ning,我可以从 C# 成功 运行 ssh 并在远程机器上获得 shell 提示。我可以键入命令并获得输出。但是我不知道如何让我输入 shell 的内容回显。当我键入 ls 并按回车键时,您看不到 ls 或按回车键后的换行符,您只能看到它的输出。我已经验证 ssh 正在分配一个 tty。目的地 shell 在交互模式下是 bash,因此在那里启用了 readline。问题必须在于 C# 如何将 STDIN 和 STDOUT 连接到 Console。 Google 没有帮助,所以我希望这里有人可以提供帮助。

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);

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 作为命令序列。

也许这就是您想要做的:

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 的单声道与 windows cmd 的行为不同,因此以下内容可能是可以接受的: 命令被回显,甚至在键入目录时选项卡也被管理(请看下面的屏幕截图)!最后请注意 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());
        }
    }
}

编辑 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

只是对我的评论进行跟进,建议使用恢复 echo 的命令(参考 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 - 是 not 应该 echo 任何东西(除非有人故意重定向标准 I/O,正如我在第一个示例和编辑 1 和 2 中所做的那样)。 Echoing 是 shell 在 Linux 和 Windows 中的一种行为:可以按照编辑 3 中所示进行管理。

我偶然发现了同样的问题,user4569980的分析帮助很大。

此行为的根本原因是单声道禁用了当前使用的 tty 的回显功能。 参见 http://www.linusakesson.net/programming/tty/ and 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

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

的简单反映

现在使用以下伪代码:

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