Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang, multiple commands in the same SSH Session

Tags:

ssh

go

I need to run multiple commands in the same SSH Session. Between each command I'm sending. I need to parse the output.

I found I can printout the first line from STDOUT, but when I try and print everything it just hangs, seeming waiting for STDOUT. This works for printing the first line, I can't figure out how to print all from STDOUT and release back to the program to run the next command.

I need to run a command, parse the output, then run a second command within the same session.

Example of my code.

package main

import (
    "bufio"
    "fmt"
    "log"

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

func main() {
    // SSH credentials
    sshConfig := &ssh.ClientConfig{
        User: "rob",
        Auth: []ssh.AuthMethod{
            ssh.Password("OMGAPASSWORD....EEEEEEE"),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

    // Connect to SSH server
    client, err := ssh.Dial("tcp", "*************:22", sshConfig)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // Open a new session
    session, err := client.NewSession()
    if err != nil {
        log.Fatal(err)
    }
    defer session.Close()

    // Set up input and output streams
    sshIn, _ := session.StdinPipe()
    sshOut, _ := session.StdoutPipe()
    //sshErr, _ := session.StderrPipe()

    // Start the SSH session
    if err := session.Start("/bin/bash"); err != nil {
        log.Fatalf("Failed to start SSH session: %v", err)
    }

    // Send the first command
    cmd1 := "ls -al"
    fmt.Fprintf(sshIn, "%s\n", cmd1)

    lines := make(chan string)

    go func() {
        scanner := bufio.NewScanner(sshOut)
        for scanner.Scan() {
            lines <- scanner.Text()
        }
        close(lines)
    }()

    for line := range lines {
        fmt.Println(line)
    }
}

like image 891
Robert YT Avatar asked May 05 '26 18:05

Robert YT


1 Answers

session.Start("/bin/bash")

Trying to run sequential commands through a persistent shell is the root of all your difficulties. Since bash is what you're reading to and writing from, you can't easily tell when each command is successful.

Instead, start a new session with each command. Copy stdout and stderr to buffers, then you can do whatever you want with them.

    for _, cmd := range []string{"ls -al", `bc <<<"3/2.0"`} {
        session, err := client.NewSession()
        if err != nil {
            log.Fatal(err)
        }
        var stdout, stderr bytes.Buffer
        // Set up input and output streams
        //sshIn, _ := session.StdinPipe()
        sshOut, _ := session.StdoutPipe()
        sshErr, _ := session.StderrPipe()
        go io.Copy(&stdout, sshOut)
        go io.Copy(&stderr, sshErr)
        // Start the command
        if err := session.Start(cmd); err != nil {
            log.Fatalf("Failed to start SSH session: %v", err)
        }
        if err := session.Wait(); err != nil {
            log.Fatal(fmt.Errorf("error waiting: %w", err))
        }
        session.Close()
        fmt.Printf("Command: \t%s\nStdout: \t%s\nStderr:%s\n", cmd, stdout.String(), stderr.String())
    }

This way you know when each sequential command ends and whose output is which.

Full example should work locally if you have an ssh key pair in ~/.ssh, but doesn't work in playground because there's no SSh server to connect to (or key pair). Start a local ssh server like this:

docker run --rm -d -e PUBLIC_KEY="$(cat ~/.ssh/id_rsa.pub)"  -e USER_NAME=$(whoami) -p 2222:2222 linuxserver/openssh-server
like image 113
Daniel Farrell Avatar answered May 07 '26 08:05

Daniel Farrell