Reputation: 13655
I am trying to call an external command (e.g., seq 10
) and take its output, process the output then print out the processed results. But the following code does not work. Could you please let me know how to make it work?
// vim: set noexpandtab tabstop=2:
package main
import (
"bufio"
"io"
"os"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("seq", "10")
stdin := bufio.NewReader(cmd.Stdout)
err := cmd.Run()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
for {
line, err := stdin.ReadBytes('\n')
if err == io.EOF {
if len(line) == 0 { break }
} else {
if err != nil { log.Fatal(err) }
line = line[:(len(line)-1)]
}
os.Stdout.Write(line)
os.Stdout.Write([]byte{'\n'})
}
}
$ $ go run main.go
# command-line-arguments
./main.go:15:30: cannot use cmd.Stdout (type io.Writer) as type io.Reader in argument to bufio.NewReader:
io.Writer does not implement io.Reader (missing Read method)
EDIT: I also tried this. But it also has error. Could anybody show me a working example.
// vim: set noexpandtab tabstop=2:
package main
import (
"bufio"
"io"
"os"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("seq", "10")
stdout, err := cmd.StdoutPipe()
if err != nil { log.Fatal(err) }
stdin := bufio.NewReader(stdout)
err = cmd.Run()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
for {
line, err := stdin.ReadBytes('\n')
if err == io.EOF {
if len(line) == 0 { break }
} else {
if err != nil { log.Fatal(err) }
line = line[:(len(line)-1)]
}
os.Stdout.Write(line)
os.Stdout.Write([]byte{'\n'})
}
}
Upvotes: 3
Views: 1816
Reputation: 370
You need to pipe the standard out to the reader using .StdoutPipe()
, you also need to use exec.Command(..).Start()
to read incrementally (.Run()
waits for the process to exit).
Here is the working code:
// vim: set noexpandtab tabstop=2:
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("seq", "10")
cmdStdOut, err := cmd.StdoutPipe()
cmdStdErr, err := cmd.StderrPipe()
defer cmdStdOut.Close()
if err != nil {
log.Fatalf("command failed with %s\n", err)
}
stdoutReader := bufio.NewReader(cmdStdOut)
stderrReader := bufio.NewReader(cmdStdErr)
err = cmd.Start()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
// Read stdout
for {
line, err := stdoutReader.ReadBytes('\n')
if err == io.EOF {
if len(line) == 0 {
break
}
} else {
if err != nil {
log.Fatal(err)
}
line = line[:(len(line) - 1)]
}
os.Stdout.Write(line)
os.Stdout.Write([]byte{'\n'})
}
// Read stderr
for {
line, err := stderrReader.ReadBytes('\n')
if err == io.EOF {
if len(line) == 0 {
break
}
} else {
if err != nil {
log.Fatal(err)
}
line = line[:(len(line) - 1)]
}
os.Stderr.Write(line)
os.Stderr.Write([]byte{'\n'})
}
cmd.Wait()
fmt.Println(cmd.ProcessState.ExitCode())
}
Upvotes: 2
Reputation: 640
Another method (and a cleaner one) is to use bufio.Scanner
which handles \n
(or any other delimiter) automatically. Another advantage is that this method doesn't have race issues (been there, done that):
package main
import (
"bufio"
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("seq", "10")
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
err = cmd.Start()
if err != nil {
log.Fatalf("cmd.Start() failed with %s\n", err)
}
stdin := bufio.NewScanner(stdout)
for stdin.Scan() {
fmt.Println(stdin.Text())
}
cmd.Wait()
}
stdin.Scan()
returns false on EOF
, which is given once the process exits. cmd.Wait()
will close StdoutPipe
, and you can read err.(exec.ExitError).ExitCode()
to get the exit code (if exited non-zero).
Upvotes: 4