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