Skip to content

Instantly share code, notes, and snippets.

@kylelemons
Forked from dagoof/piping.go
Last active July 4, 2022 01:25
Show Gist options
  • Star 57 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
  • Save kylelemons/1525278 to your computer and use it in GitHub Desktop.
Save kylelemons/1525278 to your computer and use it in GitHub Desktop.
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 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 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)
}
}
}
@vcastellm
Copy link

What I need, Thanks!

@emicklei
Copy link

Had to change the code a bit to make it work in Go 1.0.3 (os.Error => error, splitted line 31).

@CalK16
Copy link

CalK16 commented Nov 25, 2021

OMG! This is the one that I've been looking for!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment