github.com/blend/go-sdk@v1.20220411.3/sh/pipe.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package sh
     9  
    10  import (
    11  	"io"
    12  	"os"
    13  	"os/exec"
    14  	"sync"
    15  )
    16  
    17  // Pipe runs commands in sequence, piping combined output to the standard in of the next command.
    18  func Pipe(commands ...*exec.Cmd) error {
    19  	wg := sync.WaitGroup{}
    20  	wg.Add(len(commands))
    21  
    22  	errors := make(chan error, len(commands))
    23  	readers := make([]io.Reader, len(commands))
    24  	writers := make([]io.Writer, len(commands))
    25  	for index := 0; index < len(commands); index++ {
    26  		// set up pipes
    27  		readers[index], writers[index] = io.Pipe()
    28  
    29  		// wire up pipes
    30  		switch {
    31  		case index == 0: // the first command
    32  			commands[index].Stdin = os.Stdin
    33  			commands[index].Stdout = writers[index]
    34  			commands[index].Stderr = writers[index]
    35  		case index == len(commands)-1: // the last command
    36  			commands[index].Stdin = readers[index-1]
    37  			commands[index].Stdout = os.Stdout
    38  			commands[index].Stderr = os.Stderr
    39  		default: // intermediate commands
    40  			commands[index].Stdin = readers[index-1]
    41  			commands[index].Stdout = writers[index]
    42  			commands[index].Stderr = writers[index]
    43  		}
    44  		go func(index int, cmd *exec.Cmd) {
    45  			defer wg.Done()
    46  			if index > 0 {
    47  				defer func() {
    48  					if typed, ok := cmd.Stdout.(*io.PipeWriter); ok {
    49  						typed.Close()
    50  					}
    51  					if typed, ok := cmd.Stderr.(*io.PipeWriter); ok {
    52  						typed.Close()
    53  					}
    54  					if typed, ok := cmd.Stdin.(*io.PipeReader); ok {
    55  						typed.Close()
    56  					}
    57  				}()
    58  			}
    59  			if err := cmd.Run(); err != nil {
    60  				if !IsEPIPE(err) {
    61  					errors <- err
    62  				}
    63  			}
    64  		}(index, commands[index])
    65  	}
    66  	wg.Wait()
    67  	if len(errors) > 0 {
    68  		return <-errors
    69  	}
    70  	return nil
    71  }