交互式 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);
只需捕获并复制 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)
在 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);
只需捕获并复制 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.
至于另一个可能更简单的选择:
只是不要调用裸 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)