@@ -27,9 +27,9 @@ import (
2727// Pipe represents a pipe object with an associated [ReadAutoCloser].
2828type Pipe struct {
2929 // Reader is the underlying reader.
30- Reader ReadAutoCloser
31- stdout io.Writer
32- httpClient * http.Client
30+ Reader ReadAutoCloser
31+ stdout , stderr io.Writer
32+ httpClient * http.Client
3333
3434 // because pipe stages are concurrent, protect 'err'
3535 mu * sync.Mutex
@@ -369,8 +369,9 @@ func (p *Pipe) Error() error {
369369}
370370
371371// Exec runs cmdLine as an external command, sending it the contents of the
372- // pipe as input, and produces the command's combined output. The effect of
373- // this is to filter the contents of the pipe through the external command.
372+ // pipe as input, and produces the command's standard output (see below for
373+ // error output). The effect of this is to filter the contents of the pipe
374+ // through the external command.
374375//
375376// # Error handling
376377//
@@ -381,6 +382,10 @@ func (p *Pipe) Error() error {
381382// because [Pipe.String] is a no-op if the pipe's error status is set, if you
382383// want output you will need to reset the error status before calling
383384// [Pipe.String].
385+ //
386+ // If the command writes to its standard error stream, this will also go to the
387+ // pipe, along with its standard output. However, the standard error text can
388+ // instead be redirected to a supplied writer, using [Pipe.WithStderr].
384389func (p * Pipe ) Exec (cmdLine string ) * Pipe {
385390 return p .Filter (func (r io.Reader , w io.Writer ) error {
386391 args , ok := shell .Split (cmdLine ) // strings.Fields doesn't handle quotes
@@ -391,9 +396,12 @@ func (p *Pipe) Exec(cmdLine string) *Pipe {
391396 cmd .Stdin = r
392397 cmd .Stdout = w
393398 cmd .Stderr = w
399+ if p .stderr != nil {
400+ cmd .Stderr = p .stderr
401+ }
394402 err := cmd .Start ()
395403 if err != nil {
396- fmt .Fprintln (w , err )
404+ fmt .Fprintln (cmd . Stderr , err )
397405 return err
398406 }
399407 return cmd .Wait ()
@@ -429,14 +437,17 @@ func (p *Pipe) ExecForEach(cmdLine string) *Pipe {
429437 cmd := exec .Command (args [0 ], args [1 :]... )
430438 cmd .Stdout = w
431439 cmd .Stderr = w
440+ if p .stderr != nil {
441+ cmd .Stderr = p .stderr
442+ }
432443 err = cmd .Start ()
433444 if err != nil {
434- fmt .Fprintln (w , err )
445+ fmt .Fprintln (cmd . Stderr , err )
435446 continue
436447 }
437448 err = cmd .Wait ()
438449 if err != nil {
439- fmt .Fprintln (w , err )
450+ fmt .Fprintln (cmd . Stderr , err )
440451 continue
441452 }
442453 }
@@ -886,6 +897,14 @@ func (p *Pipe) WithReader(r io.Reader) *Pipe {
886897 return p
887898}
888899
900+ // WithStderr redirects the standard error output for commands run via
901+ // [Pipe.Exec] or [Pipe.ExecForEach] to the writer w, instead of going to the
902+ // pipe as it normally would.
903+ func (p * Pipe ) WithStderr (w io.Writer ) * Pipe {
904+ p .stderr = w
905+ return p
906+ }
907+
889908// WithStdout sets the pipe's standard output to the writer w, instead of the
890909// default [os.Stdout].
891910func (p * Pipe ) WithStdout (w io.Writer ) * Pipe {
0 commit comments