【问题标题】:Interactive c# System.Process not Echoing input交互式 c# System.Process 不回显输入
【发布时间】:2015-03-24 18:59:46
【问题描述】:

鉴于以下代码在 Linux 上的 Mono 中运行,我可以从 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);

【问题讨论】:

  • 在这里工作正常,在 debian stable 上使用 OpenSSH_6.0p1,单声道 3.10。你的版本是什么?
  • Ubuntu 14.06 上的单声道 3.10 OpenSSH。你得到回显的命令就好了吗?
  • 是的,请参阅screenshot。我想你是这个意思,对吧? (某些部分被审查;))
  • 嗯嗯很有趣我想知道你的环境和我的有什么不同?您使用的是哪个 .Net 框架?由于我们很快会更正的原因,我被困在 4 上。也许它在 4.5 中得到了修复?
  • 哦,这 exe的有效来源。代码在所有框架上都可以正常编译和执行(一旦删除 System.IO. 行)。回声正是描述预期行为的术语。

标签: c# linux shell ssh mono


【解决方案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 不同,因此以下可能是可以接受的: 回显命令,甚至管理键入目录时的选项卡(请看下面的屏幕截图)!最后请注意 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

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

【讨论】:

    【解决方案2】:

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

    此行为的根本原因是 mono 禁用了当前使用的 tty 的回显功能。 见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)

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 不幸的是,如果我截获 STDIN,那么它会在另一端导致问题,即 ssh 不会分配伪 TTY,bash 也不会因此禁用交互式 readline 支持。
      猜你喜欢
      • 2021-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多