Home > database >  Golang execute shell command and return as channel
Golang execute shell command and return as channel

Time:10-28

I am new to golang tried but not getting in my mind. I wanted to execute a shell command and then return the error (if exists) as a channel or stdoutput as a channel.

So far I have done this:

package main

import (
    "bufio"
    "io"
    "os"
    "os/exec"
)

type CommandRequest struct {
    Command string
    Args    string
}
type CommandResponse struct {
    StdOut chan string
    StdErr chan string
}

func main() {
    ExecuteCommand();
}
func ExecuteCommand() CommandResponse {
    cmd := exec.Command("ping", "192.168.0.1")
    _, err := cmd.Output()
    var returnValue CommandResponse
    if err != nil {
        output := make(chan string)
        go func() { output <- read(os.Stdout) }()
        returnValue = CommandResponse{output, nil} //Reading from Stdin
    }
    return returnValue
}

func read(r io.Reader) <-chan string {
    lines := make(chan string)
    go func() {
        defer close(lines)
        scan := bufio.NewScanner(r)
        for scan.Scan() {
            lines <- scan.Text()
        }
    }()
    return lines
}

Playground Link https://play.golang.org/p/pJ2R6fzK8gR I tried as much as possible to reduce the error and what is left same i am getting in my workspace as well

CodePudding user response:

There are a couple of ways to read from a command output.

1. Output Method

This is the easiest one. Get directly from Output

func main() {
    out, _ := exec.Command("./ping", "192.168.0.1").Output()
    fmt.Printf("Stdout: %s\n", string(out))
}

2. Redirect Outputs to bytes.Buffer

func main() {
    var stdout, stderr bytes.Buffer

    cmd := exec.Command("./ping", "192.168.0.1")
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr
    _ := cmd.Run()

    fmt.Printf("Stdout: %s\n", stdout.String())
    fmt.Printf("Stderr: %s\n", stderr.String())
}

2. Stdout Pipe with Channel

We generally do not return channel as in your code because their usage to hold and move data is different then variables. We pass them as argument to functions and read/write those channels from different processes (goroutines).

You can use StdoutPipe() method to get output from a pipe as io.Readcloser. Then read it with bufio.Scanner. As you can see I used channel here.

func main() {

    cmd := exec.Command("./ping", "192.168.0.1")
    cmdReader, _ := cmd.StdoutPipe()

    scanner := bufio.NewScanner(cmdReader)
    out := make(chan string)
    go reader(scanner, out)

    done := make(chan bool)
    go func() {
        value := <-out
        println(value)
        done <- true
    }()

    _ = cmd.Run()
    <-done
}

func reader(scanner *bufio.Scanner, out chan string) {
    for scanner.Scan() {
        out <- scanner.Text()
    }
}

CodePudding user response:

Channels are the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine. If buffered channel is not used, sends and receives block until the other side is ready. Returning Channel may not be appropriate channel usage.

There are two options to solve your question

Option1 : Not to use the channel and just simply return CommandResponse.

package main

import (
    "fmt"
    "os/exec"
)

type CommandRequest struct {
    Command string
    Args    string
}

func main() {
    cr := CommandRequest{"ls", "-la"}

    fmt.Println(ExecuteCommand(cr))

}


func ExecuteCommand(cr CommandRequest) string{
    cmd := exec.Command(cr.Command, cr.Args)

    stdout, err := cmd.Output()

    if err != nil {

        return err.Error()
    }

    return string(stdout)

}

Option2: Use channel but without return. Create a channel and pass it as argument and keep blocked on the receive in main(). So the ExecuteCommand() could execute the shell command and send the return status through the channel.

package main

import (
    "fmt"
    "os/exec"
)


type CommandRequest struct {
    Command string
    Args    string
}

func main() {
    cr := CommandRequest{"ls", "-la"}
    retc := make(chan string)

    go ExecuteCommand(cr, retc)

    ret := <-retc
    
    fmt.Println(ret)

}

func ExecuteCommand(cr CommandRequest, retc chan string) {
    cmd := exec.Command(cr.Command, cr.Args)

    stdout, err := cmd.Output()

    if err != nil {
        retc <- err.Error()
        return
    }

    retc <- string(stdout)
}

  • Related