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

时间:2014-06-26 21:03:23

标签: ssh go

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

7 个答案:

答案 0 :(得分:9)

虽然针对您的具体问题,您可以轻松运行sudo /path/to/script.sh,但令我震惊的是,在同一会话中运行多个命令的方法并不简单,所以我想出了一点{ {3}},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提示没有以$结尾($后跟一个空格),那么这将是僵局,因此它为什么是黑客攻击。

答案 1 :(得分:6)

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

func executeCmd(cmd []string, hostname string, config *ssh.ClientConfig) string {
conn, err := ssh.Dial("tcp", hostname+":22", 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连接中运行命令,但每次都有一个新的会话(频道)。

答案 2 :(得分:4)

您可以使用小技巧:sh -c 'cmd1&&cmd2&&cmd3&&cmd4&&etc..'

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

答案 3 :(得分:1)

这对我有用。

firstClass = mapper.readValue(manifestJSON, FirstClass.class);

答案 4 :(得分:0)

真的很喜欢OneOfOne的答案,这个答案激发了我一个更通用的解决方案,即采用一个可以匹配读取字节尾部的变量并打破阻塞读取(也不需要分叉两个额外的线程来阻止读取和写入)。已知的限制是(如在原始解决方案中)如果匹配字符串在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
}

答案 5 :(得分:0)

enter image description here

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

Tuple2

其功能与get inspiration from this

相同

这是整个代码:

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())
}

答案 6 :(得分:-3)

以下代码适用于我。

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()
}