martinrusev
10/1/2015 - 8:09 AM

piping exec.Cmd in golang (example sorts all regular files under a directory by their extension)

piping exec.Cmd in golang (example sorts all regular files under a directory by their extension)

package main

import (
        "bytes"
        "exec"
        "log"
        "os"
)

// Pipeline strings together the given exec.Cmd commands in a similar fashion
// to the Unix pipeline.  Each command's standard output is connected to the
// standard input of the next command, and the output of the final command in
// the pipeline is returned, along with the collected standard error of all
// commands and the first error found (if any).
//
// To provide input to the pipeline, assign an io.Reader to the first's Stdin.
func Pipeline(cmds ...*exec.Cmd) (pipeLineOutput, collectedStandardError []byte, pipeLineError os.Error) {
        // Require at least one command
        if len(cmds) < 1 { 
                return nil, nil, nil
        }

        // Collect the output from the command(s)
        var output bytes.Buffer
        var stderr bytes.Buffer

        last := len(cmds) - 1
        for i, cmd := range cmds[:last] {
                var err os.Error
                // Connect each command's stdin to the previous command's stdout
                if cmds[i+1].Stdin, err = cmd.StdoutPipe(); err != nil {
                        return nil, nil, err
                }
                // Connect each command's stderr to a buffer
                cmd.Stderr = &stderr
        }

        // Connect the output and error for the last command
        cmds[last].Stdout, cmds[last].Stderr = &output, &stderr

        // Start each command
        for _, cmd := range cmds {
                if err := cmd.Start(); err != nil {
                        return output.Bytes(), stderr.Bytes(), err
                }
        }

        // Wait for each command to complete
        for _, cmd := range cmds {
                if err := cmd.Wait(); err != nil {
                        return output.Bytes(), stderr.Bytes(), err
                }
        }

        // Return the pipeline output and the collected standard error
        return output.Bytes(), stderr.Bytes(), nil
}

func main() {
        // Collect directories from the command-line
        var dirs []string
        if len(os.Args) > 1 {
                dirs = os.Args[1:]
        } else {
                dirs = []string{"."}
        }

        // Run the command on each directory
        for _, dir := range dirs {
                // find $DIR -type f # Find all files
                ls := exec.Command("find", dir, "-type", "f")

                // | grep -v '/[._]' # Ignore hidden/temporary files
                visible := exec.Command("egrep", "-v", `/[._]`)

                // | sort -t. -k2 # Sort by file extension
                sort := exec.Command("sort", "-t.", "-k2")

                // Run the pipeline
                output, stderr, err := Pipeline(ls, visible, sort)
                if err != nil {
                        log.Printf("dir %q: %s", dir, err.String())
                }

                // Print the stdout, if any
                if len(output) > 0 {
                        log.Printf("%q:\n%s", dir, output)
                }

                // Print the stderr, if any
                if len(stderr) > 0 {
                        log.Printf("%q: (stderr)\n%s", dir, stderr)
                }
        }
}