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

hey*_*hey 8 ssh go

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

One*_*One 9

虽然针对您的具体问题,您可以轻松运行sudo /path/to/script.sh,但令我震惊的是,没有一种简单的方法可以在同一个会话中运行多个命令,所以我想出了一些黑客,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()
}
Run Code Online (Sandbox Code Playgroud)

如果你的shell提示符不以$($后跟一个空格)结束,那么这将是死锁,因此它为什么是hack.

  • 这应该不足为奇.SSH是一个远程"shell",您可以运行一个命令,也可以请求交互式shell/pty. (3认同)

小智 9

Session.Shell允许通过在via中传递命令来运行多个命令session.StdinPipe().

请注意,使用这种方法会使您的生活更加复杂; 而不是一次运行命令的一次性函数调用并在完成后收集输出,你需要管理你的输入缓冲区(不要忘记\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.
Run Code Online (Sandbox Code Playgroud)


小智 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()
Run Code Online (Sandbox Code Playgroud)

}

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

  • 这在您需要在命令之间保留上下文的情况下不起作用. (2认同)

cre*_*ack 5

你可以使用一个小技巧:

sh -c 'cmd1&&cmd2&&cmd3&&cmd4&&etc..'
Run Code Online (Sandbox Code Playgroud)

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