C# 从ShellStream检索输出的SSH.NET
我是SSH.NET新手,目前正在一个项目中使用它 我必须使用SSH.NET运行sudo命令,这就是为什么我要使用ShellStream来运行该命令并为sudo命令提供身份验证。现在,我尝试运行sudo命令,在我使用ssh连接到的服务器上的目录中查找文件。命令如下:C# 从ShellStream检索输出的SSH.NET,c#,ssh.net,C#,Ssh.net,我是SSH.NET新手,目前正在一个项目中使用它 我必须使用SSH.NET运行sudo命令,这就是为什么我要使用ShellStream来运行该命令并为sudo命令提供身份验证。现在,我尝试运行sudo命令,在我使用ssh连接到的服务器上的目录中查找文件。命令如下: sudo -S find -name 29172 此命令应输出一个路径,该路径指示文件的位置,如下所示: sudo -S find -name 29172 /output/directory/29172 我现在遇到的问题是,当我通
sudo -S find -name 29172
此命令应输出一个路径,该路径指示文件的位置,如下所示:
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”
。有人知道怎么做吗?感谢您的阅读,我希望很快能收到您的来信。我的解决方案相当长,但它还做了一些其他必要的事情,以便通过ssh可靠地运行命令:
- 自动响应身份验证请求
- 捕获错误代码并引发故障
Expect
——这类似于同名的linux工具,让您以交互方式响应。它会等待,直到有一些输出与模式匹配,例如密码提示
如果你有一系列的sudo操作,你可能会被不可预测的时间抓住,直到sudo需要重新验证,所以sudo可能需要也可能不需要验证,我们不能确定
自动化时的一个大问题是知道命令是否失败。知道的唯一方法是获取外壳上的最后一个错误。我掷非零
可能需要为您的配置定制与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);
}
}
}
您为输入创建了一个字符串,是否还需要为输出创建一个字符串?
stream.WriteLine(“\”+密码+“\”)代码>–为什么使用双引号?它应该是简单的stream.WriteLine(密码)代码>一般来说,自动化sudo
是一种糟糕的方法。请参阅。作为旁白,请确保您通过使用使用构造来决定性地处理流
和客户端
。我之所以没有放它,是因为我认为我的示例不需要它。ShellStream.Expect返回一个字符串,但是您的Expect方法调用var result=this.shell.Expect,然后使用result.StringAfter()和result.StringBeforeLastRegEx()方法,这两个方法都不存在。这在这里试图做什么?请参见示例中的StringExt
类。它们是用于提取子字符串的字符串扩展,例如剥离shell提示、剥离回显命令和识别密码提示。