SSH.NET извлекает выходные данные из ShellStream - PullRequest
0 голосов
/ 23 сентября 2019

Я новичок в SSH.NET и использую его в проекте, над которым я сейчас работаю.

Мне нужно выполнить команду sudo с использованием SSH.NET, и поэтому я использую ShellStream для запуска команды и обеспечения аутентификации для команды sudo.Теперь я пытаюсь запустить команду sudo, которая находит файл в каталоге на сервере, к которому я подключаю ssh.Команда выглядит следующим образом:

sudo -S find -name 29172

Эта команда должна выводить путь, который указывает, где находится файл, следующим образом:

./output/directory/29172

Проблемау меня сейчас есть то, что, когда я делаю это через поток оболочки, я не знаю, как получить вывод.И даже когда я пытаюсь прочитать ShellStream, я получаю следующее:

var client = new SshClient(IP, username, password);

var stream = client.CreateShellStream("input", 0, 0, 0, 0, 1000000);

stream.WriteLine("sudo -S find . -name 29172");

stream.WriteLine("\"" +password+"\"");

var output = stream.ReadToEnd();

Обычно вывод выдает описание того, как я вошел на сервер с помощью SSH.NET, а затем команды, которые я предоставилсистема:

"Last login: Mon Sep 23 15:23:35 2019 from 100.00.00.00\r\r\nserver@ubuntu-dev4:~$ sudo -S find . -name 29172\r\n[sudo] password for server: \r\n"

Я не ищу этот вывод, скорее я ищу фактический вывод команды, такой как "./output/directory/29172" из ShellStream.Кто-нибудь знает, как это сделать?Спасибо за чтение, и я надеюсь услышать от вас скоро.

1 Ответ

0 голосов
/ 25 сентября 2019

Мое решение довольно длинное, но оно делает несколько других необходимых вещей для надежного запуска команд через ssh:

  • автоматически отвечает на запросы аутентификации
  • захватывает коды ошибок и генерирует ошибки

Для автоматизации sudo через SSH мы можем использовать Expect - это похоже на одноименный инструмент linux и позволяет вам отвечать в интерактивном режиме.Он ожидает, пока не появится какой-либо вывод, соответствующий шаблону, например, запрос пароля.

Если у вас есть серия операций sudo, вы можете быть пойманы за непредсказуемое количество времени, пока sudo не потребует повторной аутентификации, поэтому sudo может или не можетнужна проверка подлинности, мы не можем быть уверены.

Большая проблема при автоматизации заключается в том, чтобы узнать, происходит ли сбой команды или нет.Единственный способ узнать это - получить последнюю ошибку через оболочку.Я добавляю ненулевое значение.

Регулярное выражение для соответствия приглашению оболочки, возможно, должно быть настроено для вашей конфигурации.В подсказку могут быть введены все виды вещей.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Renci.SshNet;
using Renci.SshNet.Common;

namespace Example
{
    public class Log
    {
        public static void Verbose(string message) =>
            Console.WriteLine(message);

        public static void Error(string message) =>
            Console.WriteLine(message);
    }

    public static class StringExt
    {
        public static string StringBeforeLastRegEx(this string str, Regex regex)
        {
            var matches = regex.Matches(str);

            return matches.Count > 0
                ? str.Substring(0, matches.Last().Index)
                : str;

        }

        public static bool EndsWithRegEx(this string str, Regex regex)
        {
            var matches = regex.Matches(str);

            return
                matches.Count > 0 &&
                str.Length == (matches.Last().Index + matches.Last().Length);
        }

        public static string StringAfter(this string str, string substring)
        {
            var index = str.IndexOf(substring, StringComparison.Ordinal);

            return index >= 0
                ? str.Substring(index + substring.Length)
                : "";
        }

        public static string[] GetLines(this string str) =>
            Regex.Split(str, "\r\n|\r|\n");
    }

    public static class UtilExt
    {
        public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> func) 
        {
            foreach (var item in sequence)
            {
                func(item);
            }
        }
    }

    public class Ssh
    {
        SshClient sshClient;
        ShellStream shell;
        string pwd = "";
        string lastCommand = "";

        static Regex prompt = new Regex("[a-zA-Z0-9_.-]*\\@[a-zA-Z0-9_.-]*\\:\\~[#$] ", RegexOptions.Compiled);
        static Regex pwdPrompt = new Regex("password for .*\\:", RegexOptions.Compiled);
        static Regex promptOrPwd = new Regex(Ssh.prompt + "|" + Ssh.pwdPrompt);

        public void Connect(string url, int port, string user, string pwd)
        {
            Log.Verbose($"Connect Ssh: {user}@{pwd}:{port}");

            var connectionInfo =
                new ConnectionInfo(
                    url,
                    port,
                    user,
                    new PasswordAuthenticationMethod(user, pwd));

            this.pwd = pwd;
            this.sshClient = new SshClient(connectionInfo);
            this.sshClient.Connect();

            var terminalMode = new Dictionary<TerminalModes, uint>();
            terminalMode.Add(TerminalModes.ECHO, 53);

            this.shell = this.sshClient.CreateShellStream("", 0, 0, 0, 0, 4096, terminalMode);

            try
            {
                this.Expect(Ssh.prompt);
            }
            catch (Exception ex)
            {
                Log.Error("Exception - " + ex.Message);
                throw;
            }
        }

        public void Disconnect()
        {
            Log.Verbose($"Ssh Disconnect");

            this.sshClient?.Disconnect();
            this.sshClient = null;
        }

        void Write(string commandLine)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Log.Verbose("> " + commandLine);
            Console.ResetColor(); 

            this.lastCommand = commandLine;

            this.shell.WriteLine(commandLine);
        }

        string Expect(Regex expect, double timeoutSeconds = 60.0)
        {
            var result = this.shell.Expect(expect, TimeSpan.FromSeconds(timeoutSeconds));

            if (result == null)
            {
                throw new Exception($"Timeout {timeoutSeconds}s executing {this.lastCommand}");
            }

            result = result.Contains(this.lastCommand) ? result.StringAfter(this.lastCommand) : result;
            result = result.StringBeforeLastRegEx(Ssh.prompt);
            result = result.Trim();

            result.GetLines().ForEach(x => Log.Verbose(x));

            return result;
        }

        public string Execute(string commandLine, double timeoutSeconds = 30.0)
        {
            Exception exception = null;
            var result = "";
            var errorMessage = "failed";
            var errorCode = "exception";

            try
            {
                this.Write(commandLine);
                result = this.Expect(Ssh.promptOrPwd);

                if (result.EndsWithRegEx(pwdPrompt))
                {
                    this.Write(this.pwd);
                    this.Expect(Ssh.prompt);
                }

                this.Write("echo $?");
                errorCode = this.Expect(Ssh.prompt);

                if (errorCode == "0")
                {
                    return result;    
                }
                else if (result.Length > 0)
                {
                    errorMessage = result;
                }
            }
            catch (Exception ex)
            {
                exception = ex;
                errorMessage = ex.Message;
            }

            throw new Exception($"Ssh error: {errorMessage}, code: {errorCode}, command: {commandLine}", exception);
        }
    }
}

И затем использовать это так:


var client = new Ssh(IP, 22, username, password);

var output = client.Execute("sudo -S find . -name 29172");
...