ficapy
8/10/2017 - 8:27 AM

simple Go pipe executor

simple Go pipe executor

package main

import (
	"bytes"
	"fmt"
	"log"
	"os/exec"
	"strings"
)

func main() {
	out, err := pipe("ls -l | grep io | wc -l")
	if err != nil {
		log.Println(err)
	}
	fmt.Println(out)
}

func pipe(line string) (output string, err error) {
	cmds := strings.Split(line, "|")
	lastIx := len(cmds) - 1

	var outputBuf bytes.Buffer

	type chainLink struct {
		info    string
		wait    *exec.Cmd
		execute *exec.Cmd
	}

	chain := make([]chainLink, 0, len(cmds))

	// make chain
	for ix := range cmds {
		cmd := strings.TrimSpace(cmds[ix])
		cmdArgs := strings.Split(cmd, " ")

		link := chainLink{
			info:    cmd,
			execute: exec.Command(cmdArgs[0], cmdArgs[1:]...),
		}

		if ix > 0 {
			// link out to in
			prevOut, err := chain[ix-1].execute.StdoutPipe()
			if err != nil {
				return "", fmt.Errorf("failed getting previous stdout on %q: %s", chain[ix-1].info, err)
			}
			link.execute.Stdin = prevOut

			// add wait for previous cmd
			link.wait = chain[ix-1].execute
		}

		if ix == lastIx {
			// last one writes to buffer
			link.execute.Stdout = &outputBuf
		}

		chain = append(chain, link)
	}

	// execute chain
	var link chainLink
	for {
		link, chain = chain[0], chain[1:]

		if err := link.execute.Start(); err != nil {
			return "", fmt.Errorf("failed starting on %q: %s", link.info, err)
		}

		if link.wait != nil {
			if err := link.wait.Wait(); err != nil {
				return "", fmt.Errorf("failed waiting for previous on %q: %s", link.info, err)
			}
		}

		if len(chain) == 0 {
			if err := link.execute.Wait(); err != nil {
				return "", fmt.Errorf("failed waiting on %q: %s", link.info, err)
			}

			return outputBuf.String(), nil
		}
	}
}