Интерактивный c # System.Process не повторяет ввод

1

Учитывая следующий код, запущенный в 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);
  • 0
    Здесь отлично работает, используя OpenSSH_6.0p1 в стабильной версии Debian, с моно 3.10. Какие у вас версии?
  • 0
    моно 3.10 OpenSSH на Ubuntu 14.06. Вы получаете команды, возвращаемые просто отлично?
Показать ещё 3 комментария
Теги:
ssh
mono

3 ответа

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, чтобы его эхо, но тогда 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());
        }
    }
}

Изображение 174551Изображение 174551Изображение 174551

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.

1

Я наткнулся на ту же проблему, и анализ 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)

0

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 вообще, не убивая ваше приложение.

О, но отправляя управляющие последовательности (ключи с модификатором CMD или ALT) из.NET за пределами Windows?
Э-э... Я думаю, что на самом деле это ограничение. Это часть System.Window.Forms и я не знаю, как реплицировать это поведение с помощью чистого С# за пределами Windows.

Что касается другого, и, вероятно, гораздо более простого варианта:
Просто не вызывайте голый исполняемый файл ssh. Откройте оболочку и запустите SSH внутри этой вещи. /usr/bin/bash и "-c 'ssh -ttt hostname'". Ваша проблема заключается в эмуляции TTY, поэтому просто дайте оболочке обработать это для вас. Console.TreatControlCAsInput = true; по-прежнему применяется, хотя, по крайней мере, если вы хотите пройти через Ctrl + C в качестве последовательности команд.

  • 0
    К сожалению, если я перехватываю STDIN, то это вызывает проблему на другом конце, где ssh не будет назначать псевдо-TTY и не будет bash, таким образом отключая интерактивную поддержку readline.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню