为了账号安全,请及时绑定邮箱和手机立即绑定

如何在一个会话中发送多个命令但分别保存输出

如何在一个会话中发送多个命令但分别保存输出

Go
鸿蒙传说 2022-06-13 15:17:33
我的代码应该通过 SSH 连接到远程主机(比如说路由器)并在远程主机上运行多个命令并返回输出。随附的代码经过简化,分为三个部分:Main功能:读取命令列表,然后使用ExecCommands功能 dials/ssh 到远程主机执行命令。ExecCommands函数获取远程主机 IP、命令列表和ClientConfig用于 SSH 的 SSH。然后它拨号到 IP 并一个接一个地运行命令。最后,仅在一个字符串中返回所有命令的输出InsecureClientConfigClientConfig除了创建一个用于函数的 SSH 之外实际上并没有做太多的ExecCommands函数当我只想应用一些命令或配置并保存整体结果时,该程序运行良好。我的意思是ExecCommands获取一堆命令,将它们全部推送到远程主机,然后在一个字符串中返回(或保存)应用命令的整个输出作为输出。问题:我无法单独处理每个命令的输出。例如,假设我通过使用 ExecCommands 函数将 CMD1、CMD2、CMD3……应用于远程主机#1。由于它在一个字符串中将整个输出返回给我,因此很难找到哪个输出属于哪个 CMD目标:修改或重新设计ExecCommands函数,使其为其应用的每个命令提供单独的输出。这意味着如果remote-host#1它应用 10 个命令,我应该有 10 个单独的字符串作为输出。条件/限制:我不能为命令创建任何额外的会话,并且必须在我创建的第一个 SSH 会话中应用所有命令,即不能创建多个会话并在 SSH 包中使用Run, Shell, Output,函数Start不允许重新认证。例如,我只有一个一次性密码,可用于所有远程主机。远程主机不支持类似于 Linux 中的“回显”命令远程主机不支持任何类型的 API要点:主要重点是功能ExecCommands。我放了整个代码的简化版本来给出一个想法我stdout, err := session.StdoutPipe()用来运行多个命令,这意味着 -as pipe - 只有在工作完成后才能读取 Reader。一个选项是在函数的 for 循环中使用Session.Stdoutand 。试过但没有成功。Session.StdinExecCommands
查看完整描述

3 回答

?
一只萌萌小番薯

TA贡献1795条经验 获得超7个赞

这可以正常工作:


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 on\n",

        "test routing fib-lookup virtual-router default ip 1.1.1.1\n",

        "test routing fib-lookup virtual-router default ip 2.2.2.2\n",

        "show interface ethernet1/1\n",

        "show interface ethernet1/2\n",

        "test security-policy-match protocol 6 source 1.1.1.1 destination 2.2.2.2 destination-port 443 from ZONE1 to ZONE2\n",

        "test security-policy-match protocol 6 source 10.0.0.1 destination 10.0.2.1 destination-port 443 from ZONE1 to ZONE2\n",

        "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 %v\n", 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

}



查看完整回答
反对 回复 2022-06-13
?
宝慕林4294392

TA贡献2021条经验 获得超8个赞

由于您在特殊硬件上运行的命令数量有限,并且您知道每个命令的输出模式,因此您可以使用strings.Split或regexp拆分输出。

如果您没有命令,但知道任何具有独特输出模式的快速响应命令,那么您可以在以下示例中将其echo替换为命令(编号 2)。echo


由于会话只接受对、、、或的一次调用,并且您不想为每个命令启动一个新会话:RunStartShellOutputCombinedOutput


关键是在发送命令之前使用 astrings.Builder并将其清空,并使用将会话的标准输出同时复制 到(假设您不需要会话的标准错误):sb.Reset()io.Copystrings.Builder


sb := new(strings.Builder)

go io.Copy(sb, stdout)

如果您知道每个命令要等待多少时间(已测试),则此方法有效:

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)

}

使用字符串分隔符和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("exit\n"))

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


查看完整回答
反对 回复 2022-06-13
?
翻过高山走不出你

TA贡献1875条经验 获得超3个赞

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



查看完整回答
反对 回复 2022-06-13
  • 3 回答
  • 0 关注
  • 152 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号