user1424739
user1424739

Reputation: 13655

How to call an external program and process its output?

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

Answers (2)

Ahmed Agiza
Ahmed Agiza

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

chabad360
chabad360

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

Related Questions