【问题标题】:SSH.NET retrieving output from ShellStreamSSH.NET 从 ShellStream 检索输出
【发布时间】:2019-09-23 15:44:51
【问题描述】:

我是 SSH.NET 的新手,我在我目前正在从事的项目中使用它。

我必须使用 SSH.NET 运行 sudo 命令,这就是我使用 ShellStream 运行命令并为 sudo 命令提供身份验证的原因。现在我正在尝试运行一个 sudo 命令,该命令在我 ssh 到的服务器上的目录中找到一个文件。命令如下:

sudo -S find -name 29172

该命令应该输出一个路径,指示文件所在的位置,如下所示:

./output/directory/29172

我现在遇到的问题是,当我通过 shell 流执行此操作时,我不知道如何获取输出。即使当我尝试阅读 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"

我不是在寻找这个输出,而是寻找命令的实际输出,例如来自 ShellStream 的"./output/directory/29172"。有人知道该怎么做吗?感谢您的阅读,希望尽快收到您的来信。

【问题讨论】:

  • 您为输入创建了一个字符串,是否还需要为输出创建一个字符串?
  • stream.WriteLine("\"" +password+"\""); – 为什么是双引号?应该是简单的stream.WriteLine(password);
  • 一般来说,自动化sudo 是一种不好的方法。见Allowing automatic command execution as root on Linux using SSH
  • 顺便说一句,请确保您通过使用 using 构造确定性地处理 streamclient
  • 是的 @Jesse C.Slicer 我正在使用 using 关键字谢谢你的提示。我只是没有放它,因为我认为我的示例没有必要。

标签: c# ssh.net


【解决方案1】:

我的解决方案相当冗长,但它还做了一些其他必要的事情来可靠地通过 ssh 运行命令:

  • 自动响应身份验证请求
  • 捕获错误代码并抛出故障

要通过 SSH 自动执行 sudo,我们可以使用 Expect - 这就像同名的 linux 工具,让您以交互方式响应。它一直等到有一些输出与模式匹配,例如密码提示。

如果您有一系列 sudo 操作,您可能会在 sudo 需要重新身份验证之前的不可预知的时间内被抓住,因此 sudo 可能需要也可能不需要身份验证,我们无法确定。

自动化时的一个大问题是知道命令是否失败。唯一知道的方法是通过 shell 获取最后一个错误。我抛出非零。

与 shell 提示匹配的正则表达式可能需要针对您的配置进行自定义。各种东西都可能被注入到提示中。

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");

【讨论】:

  • ShellStream.Expect 返回一个字符串,但是您的 Expect 方法调用 var result = this.shell.Expect 然后使用了不存在的 result.StringAfter() 和 result.StringBeforeLastRegEx() 方法.这是想在这里做什么?
  • 参见示例中的StringExt 类。它们是用于提取子字符串的字符串扩展,例如剥离 shell 提示,剥离回显的命令并识别密码提示。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-09
  • 2015-10-13
  • 2021-09-12
  • 1970-01-01
  • 1970-01-01
  • 2011-10-19
相关资源
最近更新 更多