如何在一次发送多个命令但单独保存输出



我的代码应该通过SSH连接到远程主机(假设路由器(并在远程主机上运行多个命令并返回输出。

附加的代码经过简化,分为三部分:

  • Main函数:读取命令列表,然后使用ExecCommands函数拨号/ssh 到远程主机以执行命令。
  • ExecCommands函数采用用于 SSH 的远程主机 IP、命令列表和 SSHClientConfig。然后它拨到 IP 并逐个运行命令。最后,仅返回一个字符串中所有命令的输出
  • InsecureClientConfig除了创建用于ExecCommands函数的SSHClientConfig之外实际上没有太多作用的函数

当我只想应用一些命令或配置并保存整个结果时,该程序运行良好。我的意思是ExecCommands获取一堆命令,将它们全部推送到远程主机,并将应用命令的整个输出返回(或保存(在一个字符串中作为输出。

问题:

  • 我无法单独处理每个命令的输出。例如,假设我应用 CMD1、CMD2、CMD3、...使用执行命令函数到远程主机 #1。由于它在一个字符串中返回了整个输出,因此很难找到哪个输出属于哪个CMD

目标:

  • 修改或重新设计ExecCommands函数,使其为应用的每个命令提供单独的输出。这意味着如果对于remote-host#1它应用 10 个命令,我应该有 10 个单独的字符串作为输出。

条件/限制:

  • 无法为命令创建任何额外的会话,并且必须在我创建的第一个SSH会话中应用所有命令,即无法创建多个会话并在SSH包中使用RunShellOutputStart函数
  • 不允许重新进行身份验证。例如,我只有一个可用于所有远程主机的一次性密码。
  • 远程主机不支持类似于 Linux 中的命令的"echo">
  • 远程主机不支持任何类型的 API

点:

  • 主要关注的是功能ExecCommands。我放了一个简化版本的整个代码来给出一个想法
  • 我正在使用stdout, err := session.StdoutPipe()来运行多个命令,这意味着 - 作为管道 - 只有在工作完成后才能读取 Reader。
  • 一个选项是在函数ExecCommandsfor 循环中使用Session.StdoutSession.Stdin。尝试但没有成功。

法典:

package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"time"
"golang.org/x/crypto/ssh"
)
func main() {
// List of the commands should be sent to the devices
listCMDs := []string{
"set cli op-command-xml-output on",
"test routing fib-lookup virtual-router default ip 1.1.1.1",
"test routing fib-lookup virtual-router default ip 2.2.2.2",
"show interface ethernet1/1",
"show interface ethernet1/2",
"test security-policy-match protocol 6 source 1.1.1.1 destination 2.2.2.2 destination-port 443 from ZONE1 to ZONE2",
"test security-policy-match protocol 6 source 10.0.0.1 destination 10.0.2.1 destination-port 443 from ZONE1 to ZONE2",
"exit",
}
sshconfig := InsecureClientConfig("admin", "admin")
s, err := ExecCommands("192.168.1.250", listCMDs, sshconfig)
fmt.Println(s, err)
}
// ExecCommands ...
func ExecCommands(ipAddr string, commands []string, sshconfig *ssh.ClientConfig) (string, error) {
// Gets IP, credentials and config/commands, SSH Config (Timeout, Ciphers, ...) and returns
// output of the device as "string" and an error. If error == nil, means program was able to SSH with no issue
// Creating outerr as Output Error.
outerr := errors.New("nil")
outerr = nil
// Creating Output as String
var outputStr string
// Dial to the remote-host
client, err := ssh.Dial("tcp", ipAddr+":22", sshconfig)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Create sesssion
session, err := client.NewSession()
if err != nil {
log.Fatal(err)
}
defer session.Close()
// StdinPipee() returns a pipe that will be connected to the remote command's standard input when the command starts.
// StdoutPipe() returns a pipe that will be connected to the remote command's standard output when the command starts.
stdin, err := session.StdinPipe()
if err != nil {
log.Fatal(err)
}
stdout, err := session.StdoutPipe()
if err != nil {
log.Fatal(err)
}
// Start remote shell
err = session.Shell()
if err != nil {
log.Fatal(err)
}
// Send the commands to the remotehost one by one.
for _, cmd := range commands {
_, err := stdin.Write([]byte(cmd + "n"))
if err != nil {
log.Fatal(err)
}
}
// Wait for session to finish
err = session.Wait()
if err != nil {
log.Fatal(err)
}
strByte, _ := ioutil.ReadAll(stdout)
outputStr = string(strByte)
return outputStr, outerr
}
// InsecureClientConfig ...
func InsecureClientConfig(userStr, passStr string) *ssh.ClientConfig {
SSHconfig := &ssh.ClientConfig{
User:    userStr,
Timeout: 5 * time.Second,
Auth:    []ssh.AuthMethod{ssh.Password(passStr)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Config: ssh.Config{
Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-cbc", "aes192-cbc",
"aes256-cbc", "3des-cbc", "des-cbc"},
KeyExchanges: []string{"diffie-hellman-group1-sha1",
"diffie-hellman-group-exchange-sha1",
"diffie-hellman-group14-sha1"},
},
}
return SSHconfig
}

这工作正常:

package main
import (
"bufio"
"errors"
"fmt"
"log"
"time"
"golang.org/x/crypto/ssh"
)
func main() {
// List of the commands should be sent to the devices
listCMDs := []string{
"set cli op-command-xml-output onn",
"test routing fib-lookup virtual-router default ip 1.1.1.1n",
"test routing fib-lookup virtual-router default ip 2.2.2.2n",
"show interface ethernet1/1n",
"show interface ethernet1/2n",
"test security-policy-match protocol 6 source 1.1.1.1 destination 2.2.2.2 destination-port 443 from ZONE1 to ZONE2n",
"test security-policy-match protocol 6 source 10.0.0.1 destination 10.0.2.1 destination-port 443 from ZONE1 to ZONE2n",
"exit",
}
sshconfig := InsecureClientConfig("admin", "Ghazanfar1!")
s, _ := ExecCommands("192.168.1.249", listCMDs, sshconfig)
for _, item := range s {
fmt.Println(item)
fmt.Println("-------------------------------")
}
}
// ExecCommands ...
func ExecCommands(ipAddr string, commands []string, sshconfig *ssh.ClientConfig) ([]string, error) {
// Gets IP, credentials and config/commands, SSH Config (Timeout, Ciphers, ...) and returns
// output of the device as "string" and an error. If error == nil, means program was able to SSH with no issue
// Creating outerr as Output Error.
outerr := errors.New("nil")
outerr = nil
// Creating Output as String
var outputStr []string
var strTmp string
// Dial to the remote-host
client, err := ssh.Dial("tcp", ipAddr+":22", sshconfig)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Create sesssion
session, err := client.NewSession()
if err != nil {
log.Fatal(err)
}
defer session.Close()
// StdinPipee() returns a pipe that will be connected to the remote command's standard input when the command starts.
// StdoutPipe() returns a pipe that will be connected to the remote command's standard output when the command starts.
stdin, err := session.StdinPipe()
if err != nil {
log.Fatal(err)
}
stdout, err := session.StdoutPipe()
if err != nil {
log.Fatal(err)
}
// Start remote shell
err = session.Shell()
if err != nil {
log.Fatal(err)
}
stdinLines := make(chan string)
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
stdinLines <- scanner.Text()
}
if err := scanner.Err(); err != nil {
log.Printf("scanner failed: %v", err)
}
close(stdinLines)
}()
// Send the commands to the remotehost one by one.
for i, cmd := range commands {
_, err := stdin.Write([]byte(cmd + "n"))
if err != nil {
log.Fatal(err)
}
if i == len(commands)-1 {
_ = stdin.Close() // send eof
}
// wait for command to complete
// we'll assume the moment we've gone 1 secs w/o any output that our command is done
timer := time.NewTimer(0)
InputLoop:
for {
timer.Reset(time.Second)
select {
case line, ok := <-stdinLines:
if !ok {
log.Println("Finished processing")
break InputLoop
}
strTmp += line
strTmp += "n"
case <-timer.C:
break InputLoop
}
}
outputStr = append(outputStr, strTmp)
//log.Printf("Finished processing %vn", cmd)
strTmp = ""
}
// Wait for session to finish
err = session.Wait()
if err != nil {
log.Fatal(err)
}
return outputStr, outerr
}
// InsecureClientConfig ...
func InsecureClientConfig(userStr, passStr string) *ssh.ClientConfig {
SSHconfig := &ssh.ClientConfig{
User:    userStr,
Timeout: 5 * time.Second,
Auth:    []ssh.AuthMethod{ssh.Password(passStr)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Config: ssh.Config{
Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-cbc", "aes192-cbc",
"aes256-cbc", "3des-cbc", "des-cbc"},
KeyExchanges: []string{"diffie-hellman-group1-sha1",
"diffie-hellman-group-exchange-sha1",
"diffie-hellman-group14-sha1"},
},
}
return SSHconfig
}

由于在特殊硬件上运行的命令数量有限,并且您知道每个命令输出的模式,因此您可以使用strings.Splitregexp来拆分输出.
如果您没有echo命令,但知道任何具有唯一输出模式的快速响应命令,那么您可以在以下示例(数字 2(中将其替换为echo命令。


由于会话只接受对RunStartShellOutputCombinedOutput一次调用,并且您不希望每个命令启动新会话:

关键是使用strings.Builder并清空它,使用sb.Reset()用于发送命令,并使用io.Copy将会话的 stdout 并复制到strings.Builder中(假设您不需要会话的 stderr(:

sb := new(strings.Builder)
go io.Copy(sb, stdout)
  1. 如果您知道每个命令(经过测试(等待多少时间,则此方法有效:
sb := new(strings.Builder)
go io.Copy(sb, stdout)
commands := []string{"uname -a", "sleep 1", "pwd", "whoami", "exit"}
wait := []time.Duration{10, 1200, 20, 10, 10} // * time.Millisecond
ans := []string{}
time.Sleep(10 * time.Millisecond) // wait for the ssh greetings
// Send the commands to the remotehost one by one.
for i, cmd := range commands {
sb.Reset()
fmt.Println("*** command:t", cmd)
_, err := stdin.Write([]byte(cmd + "n"))
if err != nil {
log.Fatal(err)
}
time.Sleep(wait[i] * time.Millisecond) // wait for the command to finish
s := sb.String()
fmt.Println("*** response:t", s)
ans = append(ans, s)
}

  1. 使用字符串分隔符和strings.Split(注意:您可以将echo替换为任何具有已知输出模式的快速命令(:
sb := new(strings.Builder)
go io.Copy(sb, stdout)
commands := []string{"uname -a", "sleep 1", "pwd", "whoami"}
delim := "********--------========12345678"
for _, cmd := range commands {
_, err = stdin.Write([]byte("echo " + delim + "n"))
if err != nil {
log.Fatal(err)
}
_, err := stdin.Write([]byte(cmd + "n"))
if err != nil {
log.Fatal(err)
}
}
_, err = stdin.Write([]byte("exitn"))
if err != nil {
log.Fatal(err)
}
err = session.Wait() // Wait for session to exit
if err != nil {
log.Fatal(err)
}
ans := strings.Split(sb.String(), delim)
ans = ans[1:] // remove ssh greetings

看看这个: https://github.com/yahoo/vssh 您可以将会话设置为需要同时运行的命令数,然后通过run方法将每个命令发送到远程主机并单独获得结果!

相关内容

最新更新