Golang ssh - how to run multiple commands in one session?

I am trying to run several commands using ssh , but it seems that Session.Run allows only one command per session (if I'm not mistaken). I am wondering how I can get around this limitation and reuse the session or send a sequence of commands. The reason is because I need to run sudo su in the same session with the following command (sh / usr / bin / myscript.sh)

+7
ssh go
source share
6 answers

While for your specific problem you can easily run sudo /path/to/script.sh , it shock me that there was no easy way to run multiple commands in one session, so I came up with a little 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() } 

If your shell invitation does not end with $ ($ followed by a space), it will be inhibited, so why does it crack.

+7
source share

NewSession is a connection method. You do not need to create a new connection every time. It seems that the session is that this library calls the channel for the client, and many channels are multiplexed in one connection. Consequently:

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

}

So, you open a new session (channel), and you run the command in an existing ssh connection, but each time with a new session (channel).

+5
source share

You can use a little trick: sh -c 'cmd1&&cmd2&&cmd3&&cmd4&&etc..'

This is the only command, the actual commands are passed as an argument to the shell that will execute them. So Docker processes several commands.

+4
source share

Session.Shell allows you to run multiple commands by passing your commands through session.StdinPipe() .

Remember that using this approach will complicate your life; instead of making a one-shot call to the function that launches the command and collects the output after it is completed, you need to control the input buffer (do not forget \n at the end of the command), wait for the exit to actually return from the SSH server, and then process this conclusion (if you had several teams in flight and you want to know which result belongs to what it introduces, you will need to make a plan to understand this).

 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. 
+3
source share

I really liked the OneOfOne answer, which inspired me to a more generalized solution for making a variable that could match the tail of the bytes read and interrupt the lock reading (there is also no need to expand two additional threads to lock reading and writing). A known limitation (as in the original solution), if the match string appears after 64 * 1024 bytes, then this code will rotate forever.

 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 } 
0
source share

The following code works for me.

 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() } 
-2
source share

All Articles