鉴于以下代码在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);
答案 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());
}
}
}
编辑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)
只需捕获并复制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作为命令序列。