Golang ssh -如何在同一会话上运行多个命令?

ct2axkht  于 2023-04-18  发布在  Go
关注(0)|答案(8)|浏览(541)

我试图通过ssh运行多个命令,但似乎Session.Run每个会话只允许一个命令(除非我错了)。我想知道如何绕过这个限制,重用会话或发送一系列命令。原因是我需要在同一会话中运行sudo su下一个命令(sh /usr/bin/myscript.sh)

4zcjmb1e

4zcjmb1e1#

通过session.StdinPipe()传递命令,session.shell允许运行多个命令。
要知道,使用这种方法会让你的生活变得更加复杂;您需要管理输入缓冲区,而不是使用一次函数调用来运行命令并在命令完成后收集输出(不要忘记命令末尾的\n),等待SSH服务器实际返回输出,然后适当地处理该输出(如果您有多个命令在运行中,并且想知道哪个输出属于哪个输入,那么您需要有一个计划来解决这个问题)。

stdinBuf, _ := session.StdinPipe()
err := session.Shell()
stdinBuf.Write([]byte("cd /\n"))
// The command has been sent to the device, but you haven't gotten output back yet.
// Not that you can't send more commands immediately.
stdinBuf.Write([]byte("ls\n"))
// Then you'll want to wait for the response, and watch the stdout buffer for output.
68de4m5k

68de4m5k2#

虽然对于您的特定问题,您可以轻松地运行sudo /path/to/script.sh,但令我震惊的是,没有一种简单的方法可以在同一会话上运行多个命令,所以我想出了一个hack,YMMV:

func MuxShell(w io.Writer, r io.Reader) (chan<- string, <-chan string) {
    in := make(chan string, 1)
    out := make(chan string, 1)
    var wg sync.WaitGroup
    wg.Add(1) //for the shell itself
    go func() {
        for cmd := range in {
            wg.Add(1)
            w.Write([]byte(cmd + "\n"))
            wg.Wait()
        }
    }()
    go func() {
        var (
            buf [65 * 1024]byte
            t   int
        )
        for {
            n, err := r.Read(buf[t:])
            if err != nil {
                close(in)
                close(out)
                return
            }
            t += n
            if buf[t-2] == '$' { //assuming the $PS1 == 'sh-4.3$ '
                out <- string(buf[:t])
                t = 0
                wg.Done()
            }
        }
    }()
    return in, out
}

func main() {
    config := &ssh.ClientConfig{
        User: "kf5",
        Auth: []ssh.AuthMethod{
            ssh.Password("kf5"),
        },
    }
    client, err := ssh.Dial("tcp", "127.0.0.1:22", config)
    if err != nil {
        panic(err)
    }

    defer client.Close()
    session, err := client.NewSession()

    if err != nil {
        log.Fatalf("unable to create session: %s", err)
    }
    defer session.Close()

    modes := ssh.TerminalModes{
        ssh.ECHO:          0,     // disable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
        log.Fatal(err)
    }

    w, err := session.StdinPipe()
    if err != nil {
        panic(err)
    }
    r, err := session.StdoutPipe()
    if err != nil {
        panic(err)
    }
    in, out := MuxShell(w, r)
    if err := session.Start("/bin/sh"); err != nil {
        log.Fatal(err)
    }
    <-out //ignore the shell output
    in <- "ls -lhav"
    fmt.Printf("ls output: %s\n", <-out)

    in <- "whoami"
    fmt.Printf("whoami: %s\n", <-out)

    in <- "exit"
    session.Wait()
}

如果你的shell提示符不是以$($后跟一个空格)结尾,这将死锁,这就是为什么它是一个黑客。

bbmckpt7

bbmckpt73#

NewSession是一个连接的方法。你不需要每次都创建一个新的连接。一个Session似乎是这个库为客户端调用的一个通道,并且许多通道在一个连接中被复用。因此:

func executeCmd(cmd []string, hostname string, config *ssh.ClientConfig) string {
    conn, err := ssh.Dial("tcp", hostname+":8022", config)

    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    var stdoutBuf bytes.Buffer

    for _, command := range cmd {

        session, err := conn.NewSession()

        if err != nil {
            log.Fatal(err)
        }
        defer session.Close()

        session.Stdout = &stdoutBuf
        session.Run(command)
    }

    return hostname + ": " + stdoutBuf.String()
}

因此,您打开一个新的会话(通道),并在现有的ssh连接中运行command,但每次都使用一个新的会话(通道)。

bjp0bcyl

bjp0bcyl4#

你可以使用一个小技巧:

sh -c 'cmd1&&cmd2&&cmd3&&cmd4&&etc..'

这是一个单一的命令,实际的命令作为参数传递给shell,shell将执行它们。这就是Docker处理多个命令的方式。

wwtsj6pe

wwtsj6pe5#

这对我很有效。

package main

import (
    "fmt"
    "golang.org/x/crypto/ssh"
    // "io"
    "log"
    "os"
    // Uncomment to store output in variable
    //"bytes"
)

type MachineDetails struct {
    username, password, hostname, port string
}

func main() {

    h1 := MachineDetails{"root", "xxxxx", "x.x.x.x", "22"}

    // Uncomment to store output in variable
    //var b bytes.Buffer
    //sess.Stdout = &amp;b
    //sess.Stderr = &amp;b

    commands := []string{
        "pwd",
        "whoami",
        "echo 'bye'",
        "exit",
    }

    connectHost(h1, commands)

    // Uncomment to store in variable
    //fmt.Println(b.String())

}

func connectHost(hostParams MachineDetails, commands []string) {

    // SSH client config
    config := &ssh.ClientConfig{
        User: hostParams.username,
        Auth: []ssh.AuthMethod{
            ssh.Password(hostParams.password),
        },
        // Non-production only
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

    // Connect to host
    client, err := ssh.Dial("tcp", hostParams.hostname+":"+hostParams.port, config)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // Create sesssion
    sess, err := client.NewSession()
    if err != nil {
        log.Fatal("Failed to create session: ", err)
    }
    defer sess.Close()

    // Enable system stdout
    // Comment these if you uncomment to store in variable
    sess.Stdout = os.Stdout
    sess.Stderr = os.Stderr

    // StdinPipe for commands
    stdin, err := sess.StdinPipe()
    if err != nil {
        log.Fatal(err)
    }

    // Start remote shell
    err = sess.Shell()
    if err != nil {
        log.Fatal(err)
    }

    // send the commands

    for _, cmd := range commands {
        _, err = fmt.Fprintf(stdin, "%s\n", cmd)
        if err != nil {
            log.Fatal(err)
        }
    }

    // Wait for sess to finish
    err = sess.Wait()
    if err != nil {
        log.Fatal(err)
    }

    // return sess, stdin, err
}

func createSession() {

}
r7knjye2

r7knjye26#

非常喜欢OneOfOne的答案,它启发了我一个更通用的解决方案,即采用一个变量,该变量可以匹配读取字节的尾部并打破阻塞读取(也不需要fork两个额外的线程来阻止读取和写入)。已知的限制是(与原始解决方案一样)如果匹配的字符串在64 * 1024字节之后,那么this code将永远旋转。

package main

import (
    "fmt"
    "golang.org/x/crypto/ssh"
    "io"
    "log"
)

var escapePrompt = []byte{'$', ' '}

func main() {
    config := &ssh.ClientConfig{
        User: "dummy",
        Auth: []ssh.AuthMethod{
            ssh.Password("dummy"),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }
    client, err := ssh.Dial("tcp", "127.0.0.1:22", config)
    if err != nil {
        panic(err)
    }

    defer client.Close()
    session, err := client.NewSession()

    if err != nil {
        log.Fatalf("unable to create session: %s", err)
    }
    defer session.Close()

    modes := ssh.TerminalModes{
        ssh.ECHO:          0,     // disable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
        log.Fatal(err)
    }

    w, err := session.StdinPipe()
    if err != nil {
        panic(err)
    }
    r, err := session.StdoutPipe()
    if err != nil {
        panic(err)
    }
    if err := session.Start("/bin/sh"); err != nil {
        log.Fatal(err)
    }
    readUntil(r, escapePrompt) //ignore the shell output

    write(w, "ls -lhav")

    out, err := readUntil(r, escapePrompt)

    fmt.Printf("ls output: %s\n", *out)

    write(w, "whoami")

    out, err = readUntil(r, escapePrompt)

    fmt.Printf("whoami: %s\n", *out)

    write(w, "exit")

    session.Wait()
}

func write(w io.WriteCloser, command string) error {
    _, err := w.Write([]byte(command + "\n"))
    return err
}

func readUntil(r io.Reader, matchingByte []byte) (*string, error) {
    var buf [64 * 1024]byte
    var t int
    for {
        n, err := r.Read(buf[t:])
        if err != nil {
            return nil, err
        }
        t += n
        if isMatch(buf[:t], t, matchingByte) {
            stringResult := string(buf[:t])
            return &stringResult, nil
        }
    }
}

func isMatch(bytes []byte, t int, matchingBytes []byte) bool {
    if t >= len(matchingBytes) {
        for i := 0; i < len(matchingBytes); i++ {
            if bytes[t - len(matchingBytes) + i] != matchingBytes[i] {
                return false
            }
        }
        return true
    }
    return false
}
kulphzqa

kulphzqa7#

get inspiration from this
我花了几天的时间,这个答案激励我尝试使用sdtin来运行多个命令,最后成功了。我想说我根本不知道golang,因此它可能是多余的,但代码工作。

if _, err := w.Write([]byte("sys\r")); err != nil {
    panic("Failed to run: " + err.Error())
}
if _, err := w.Write([]byte("wlan\r")); err != nil {
    panic("Failed to run: " + err.Error())
}
if _, err := w.Write([]byte("ap-id 2099\r")); err != nil {
    panic("Failed to run: " + err.Error())
}

if _, err := w.Write([]byte("ap-group xuebao-free\r")); err != nil {
    panic("Failed to run: " + err.Error())
}
if _, err := w.Write([]byte("y\r")); err != nil {
    panic("Failed to run: " + err.Error())
}

其功能与terminal operation相同
下面是整个代码:

/* switch ssh
 */
package main

import (
    "flag"
    "fmt"
    "io"
    "log"
    "net"
    "os"
    "strings"
    "sync"
)

import (
    "golang.org/x/crypto/ssh"
)

func main() {
    //go run ./testConfig.go --username="aaa" --passwd='aaa' --ip_port="192.168.6.87" --cmd='display version'
    username := flag.String("username", "aaa", "username")
    passwd := flag.String("passwd", "aaa", "password")
    ip_port := flag.String("ip_port", "1.1.1.1:22", "ip and port")
    cmdstring := flag.String("cmd", "display arp statistics all", "cmdstring")
    flag.Parse()
    fmt.Println("username:", *username)
    fmt.Println("passwd:", *passwd)
    fmt.Println("ip_port:", *ip_port)
    fmt.Println("cmdstring:", *cmdstring)
    config := &ssh.ClientConfig{
        User: *username,
        Auth: []ssh.AuthMethod{
            ssh.Password(*passwd),
        },
        Config: ssh.Config{
            Ciphers: []string{"aes128-cbc", "aes128-ctr"},
        },
        HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
            return nil
        },
    }
    // config.Config.Ciphers = append(config.Config.Ciphers, "aes128-cbc")
    clinet, err := ssh.Dial("tcp", *ip_port, config)
    checkError(err, "connet "+*ip_port)

    session, err := clinet.NewSession()
    defer session.Close()
    checkError(err, "creae shell")

    modes := ssh.TerminalModes{
        ssh.ECHO:          1,     // disable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    if err := session.RequestPty("vt100", 80, 40, modes); err != nil {
        log.Fatal(err)
    }

    w, err := session.StdinPipe()
    if err != nil {
        panic(err)
    }
    r, err := session.StdoutPipe()
    if err != nil {
        panic(err)
    }
    e, err := session.StderrPipe()
    if err != nil {
        panic(err)
    }

    in, out := MuxShell(w, r, e)
    if err := session.Shell(); err != nil {
        log.Fatal(err)
    }
    <-out //ignore the shell output
    in <- *cmdstring
    fmt.Printf("%s\n", <-out)

    if _, err := w.Write([]byte("sys\r")); err != nil {
        panic("Failed to run: " + err.Error())
    }
if _, err := w.Write([]byte("wlan\r")); err != nil {
        panic("Failed to run: " + err.Error())
    }
if _, err := w.Write([]byte("ap-id 2099\r")); err != nil {
        panic("Failed to run: " + err.Error())
    }

if _, err := w.Write([]byte("ap-group xuebao-free\r")); err != nil {
        panic("Failed to run: " + err.Error())
    }
if _, err := w.Write([]byte("y\r")); err != nil {
        panic("Failed to run: " + err.Error())
    }

    in <- "quit"
    _ = <-out
    session.Wait()
}

func checkError(err error, info string) {
    if err != nil {
        fmt.Printf("%s. error: %s\n", info, err)
        os.Exit(1)
    }
}

func MuxShell(w io.Writer, r, e io.Reader) (chan<- string, <-chan string) {
    in := make(chan string, 5)
    out := make(chan string, 5)
    var wg sync.WaitGroup
    wg.Add(1) //for the shell itself
    go func() {
        for cmd := range in {
            wg.Add(1)
            w.Write([]byte(cmd + "\n"))
            wg.Wait()
        }
    }()

    go func() {
        var (
            buf [1024 * 1024]byte
            t   int
        )
        for {
            n, err := r.Read(buf[t:])
            if err != nil {
                fmt.Println(err.Error())
                close(in)
                close(out)
                return
            }
            t += n
            result := string(buf[:t])
            if strings.Contains(string(buf[t-n:t]), "More") {
                w.Write([]byte("\n"))
            }
            if strings.Contains(result, "username:") ||
                strings.Contains(result, "password:") ||
                strings.Contains(result, ">") {
                out <- string(buf[:t])
                t = 0
                wg.Done()
            }
        }
    }()
    return in, out
}
xmq68pz9

xmq68pz98#

下面的代码适合我。

func main() {
    key, err := ioutil.ReadFile("path to your key file")
    if err != nil {
        panic(err)
    }
    signer, err := ssh.ParsePrivateKey([]byte(key))
    if err != nil {
        panic(err)
    }
    config := &ssh.ClientConfig{
        User: "ubuntu",
        Auth: []ssh.AuthMethod{
            ssh.PublicKeys(signer),
        },
    }
    client, err := ssh.Dial("tcp", "52.91.35.179:22", config)
    if err != nil {
        panic(err)
    }
    session, err := client.NewSession()
    if err != nil {
        panic(err)
    }
    defer session.Close()
    session.Stdout = os.Stdout
    session.Stderr = os.Stderr
    session.Stdin = os.Stdin
    session.Shell()
    session.Wait()
}

相关问题