func (s *Subshell) Run() (result *RunResult) {
	tmpFile := writeFile(s.program.Lessons())
	defer func() {
		attempts := 6
		var err error
		for i := 0; i < attempts; i++ {
			if i > 0 {
				time.Sleep(1 * time.Second)
			}
			err = os.Remove(tmpFile.Name())
			if err == nil {
				return
			}
		}
		msg := fmt.Sprintf(
			"After %d attempts, unable to delete %s, error=%v",
			attempts, tmpFile.Name(), err)
		if runtime.GOOS == "windows" {
			log.Print(msg)
			return
		}
		log.Fatal(msg)
	}()
	shell := exec.Command("bash", tmpFile.Name())
	stdIn, err := shell.StdinPipe()
	util.Check("in pipe", err)
	util.Check("close shell's stdin", stdIn.Close())
	stdOut, err := shell.StdoutPipe()
	util.Check("out pipe", err)
	stdErr, err := shell.StderrPipe()
	util.Check("err pipe", err)
	err = shell.Start()
	util.Check("shell start", err)
	pid := shell.Process.Pid
	if glog.V(2) {
		glog.Infof("Run: pid = %d", pid)
	}
	pgid, err := util.GetProcesssGroupID(pid)
	if err == nil {
		if glog.V(2) {
			glog.Infof("Run:  pgid = %d", pgid)
		}
	}
	result = processShellOutput(
		s.program.Lessons(),
		makeAccumulator(s.blockTimeout, "stdOut", stdOut),
		makeAccumulator(s.blockTimeout, "stdErr", stdErr))
	err = politeWait(shell)
	if glog.V(2) {
		glog.Info("Run:  Shell done.")
	}
	if err == nil {
		if result.HasProgrammerError() {
			err = errors.New("unexpected programmer error - need code fix")
		} else if !result.Completed() {
			err = errors.New("problem processing stdout and/or stderr")
		}
	}
	result.SetError(err)
	return
}
Run runs command blocks in a subprocess, stopping and
reporting on any error.

Command blocks are strings presumably holding code from some shell
language.  The strings may be more complex than single commands
delimitted by linefeeds - e.g. blocks that operate on HERE
documents, or multi-line commands using line continuation via '\',
quotes or curly brackets.

This function itself is not a shell interpreter, so it cannot know
if one line of text from a command block is an individual command
or part of something else.

Error reporting works by discarding output from command blocks that
succeeded, and only reporting the contents of stdout and stderr
when the subprocess exits on error.
package subshell
import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"
	"github.com/golang/glog"
	"github.com/monopole/mdrip/program"
	"github.com/monopole/mdrip/scanner"
	"github.com/monopole/mdrip/util"
)
type Subshell struct {
	blockTimeout time.Duration
	program      *program.Program
}
func NewSubshell(timeout time.Duration, p *program.Program) *Subshell {
	return &Subshell{timeout, p}
}
func accumulateOutput(prefix string, in <-chan string) <-chan *BlockOutput {
	out := make(chan *BlockOutput)
	var accum bytes.Buffer
	go func() {
		defer close(out)
		for line := range in {
			if strings.HasPrefix(line, scanner.MsgTimeout) {
				accum.WriteString("\n" + line + "\n")
				accum.WriteString("A subprocess might still be running.\n")
				if glog.V(2) {
					glog.Infof("accum %s: Timeout return.", prefix)
				}
				out <- NewIncompleteOutput(accum.String())
				return
			}
			if strings.HasPrefix(line, scanner.MsgError) {
				accum.WriteString(line + "\n")
				if glog.V(2) {
					glog.Infof("accum %s: Error return.", prefix)
				}
				out <- NewIncompleteOutput(accum.String())
				return
			}
			if strings.HasPrefix(line, scanner.MsgHappy) {
				if glog.V(2) {
					glog.Infof("accum %s: %s", prefix, line)
				}
				out <- NewCompleteOutput(accum.String())
				accum.Reset()
			} else {
				if glog.V(2) {
					glog.Infof("accum %s: [%s]", prefix, line)
				}
				accum.WriteString(line + "\n")
			}
		}
		if glog.V(2) {
			glog.Infof("accum %s closed.", prefix)
		}
		trailing := strings.TrimSpace(accum.String())
		if len(trailing) > 0 {
			if glog.V(2) {
				glog.Infof(
					"accum from %s: Extra output after happy echo [%s]",
					prefix, trailing)
			}
			out <- NewIncompleteOutput(trailing)
		}
	}()
	return out
}
func processShellOutput(
	lessons []*program.LessonPgm,
	chAccOut, chAccErr <-chan *BlockOutput) *RunResult {
	var prevOut, prevErr *BlockOutput
	for _, lesson := range lessons {
		numBlocks := len(lesson.Blocks())
		for i, block := range lesson.Blocks() {
			glog.Infof("Expecting output of %s (%d/%d) from %s\n",
				block.Name(), i+1, numBlocks, lesson.Path())
			if glog.V(2) {
				glog.Infof("\n%s", block.Code())
			}
			outBlock := <-chAccOut
			errBlock := <-chAccErr
			if outBlock == nil || !outBlock.Completed() ||
				errBlock == nil || !errBlock.Completed() {
				return NewRunResult(
					outBlock, errBlock).SetFileName(lesson.Path()).SetIndex(i).SetBlock(block)
			}
			prevOut = outBlock
			prevErr = errBlock
		}
	}
	glog.Info("All done, no errors triggered.")
	return NewRunResult(prevOut, prevErr)
}
func writeString(writer io.Writer, output string) {
	n, err := writer.Write([]byte(output))
	if err != nil {
		glog.Fatalf("Could not write %d bytes: %v", len(output), err)
	}
	if n != len(output) {
		glog.Fatalf("Expected to write %d bytes, wrote %d", len(output), n)
	}
}
func writeFile(lessons []*program.LessonPgm) *os.File {
	f, err := ioutil.TempFile("", "mdrip-file-")
	util.Check("create temp file", err)
	util.Check("chmod temp file", os.Chmod(f.Name(), 0744))
	writeString(f, "set -e\n")
	writeString(f, "set -u\n")
	writeString(f, "set -o pipefail\n")
	for _, lesson := range lessons {
		for _, block := range lesson.Blocks() {
			writeString(f, block.Code().String())
			writeString(f, "echo "+scanner.MsgHappy+" "+block.Name()+"\n")
			writeString(f, "echo "+scanner.MsgHappy+" "+block.Name()+" 1>&2\n\n")
		}
	}
	if glog.V(2) {
		glog.Infof("Run: running commands from %s", f.Name())
	}
	return f
}
func makeAccumulator(
	wait time.Duration, name string, stream io.ReadCloser) <-chan *BlockOutput {
	return accumulateOutput(name, scanner.BuffScanner(wait, name, stream))
}
func politeWait(shell *exec.Cmd) (err error) {
	done := make(chan error, 1)
	err = nil
	go func() {
		if glog.V(2) {
			glog.Info("Run:  Waiting for shell to end.")
		}
		done <- shell.Wait()
		glog.Info("Run:  Wait completed.")
	}()
	select {
	case <-time.After(2 * time.Second):
		glog.Infof("Run:  killing the shell after a polite wait")
		err = shell.Process.Kill()
		if err == nil {
			err = errors.New("shell timed out")
		}  
	case err = <-done:
		if err != nil {
			glog.Infof("Run:  Shell failed with error %v.", err)
		}
	}
	return
}