Учитывая следующий код, запущенный в Mono в Linux, я могу успешно запустить ssh с С# и получить приглашение оболочки в удаленном поле. Я могу вводить команды и получать выходные данные. Однако я не могу понять, как получить то, что я набираю в эту оболочку, чтобы откликнуться. Когда я ls
и нажимаю enter, вы не видите, что ls
или новая ls
попадают в клавишу ввода, вы видите только ее выход. Я подтвердил, что ssh назначает tty. Платформа назначения - bash в интерактивном режиме, поэтому в нее включена функция чтения. Проблема заключается в том, как С# подключает 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);
Возможно, именно это вы пытаетесь сделать:
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, чтобы его эхо, но тогда cmd в окнах будет разрабатывать его по строкам (даже если вы используете Console.ReadKey() => process.StandardInput.Write), поэтому у вас не может быть оболочки (покажите этот вопрос/ответ, если вы хотите копать). Но mono с linux ssh ведет себя иначе, чем 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());
}
}
}
EDIT 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
Просто следуйте моим комментариям с помощью предлагаемой команды для восстановления эха (ссылка здесь). Это изменение кода:
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
Посмотрите на этот ответ тоже.
В заключение источник, который вы показываете, а точнее, С# System.Process - не должен ничего эха (ни один из них не намеренно перенаправляет стандартный ввод-вывод, как я сделал здесь в первом примере и в редакции 1 и 2). Эхо-поведение - это поведение оболочки, как в Linux, так и в Windows: управление ею можно выполнить, как показано в edit 3.
Я наткнулся на ту же проблему, и анализ 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 # в С#. Это в основном простое отражение для bool System.ConsoleDriver.SetEcho(bool enable)
.
Теперь используйте следующий псевдо-код:
setEcho(true) var p = startProcess() p.WaitForExit() setEcho(false)
Просто захватите и дублируйте 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
вообще, не убивая ваше приложение.
О, но отправляя управляющие последовательности (ключи с модификатором CMD или ALT) из.NET за пределами Windows?
Э-э... Я думаю, что на самом деле это ограничение. Это часть System.Window.Forms
и я не знаю, как реплицировать это поведение с помощью чистого С# за пределами Windows.
Что касается другого, и, вероятно, гораздо более простого варианта:
Просто не вызывайте голый исполняемый файл ssh. Откройте оболочку и запустите SSH внутри этой вещи. /usr/bin/bash
и "-c 'ssh -ttt hostname'"
. Ваша проблема заключается в эмуляции TTY, поэтому просто дайте оболочке обработать это для вас. Console.TreatControlCAsInput = true;
по-прежнему применяется, хотя, по крайней мере, если вы хотите пройти через Ctrl + C в качестве последовательности команд.