11package shell
22
33import (
4- "bufio"
54 "fmt"
6- "io"
75 "os"
86 "os/exec"
97 "strings"
@@ -27,74 +25,47 @@ func RunShellCommand(options *ShellOptions, command string, args ...string) erro
2725 return errors .WithStackTrace (cmd .Run ())
2826}
2927
28+ // Run the specified shell command with the specified arguments. Return its stdout, stderr, and interleaved output as
29+ // separate strings in a struct.
30+ func RunShellCommandAndGetOutputStruct (options * ShellOptions , command string , args ... string ) (* Output , error ) {
31+ return runShellCommand (options , false , command , args ... )
32+ }
33+
3034// Run the specified shell command with the specified arguments. Return its stdout and stderr as a string
3135func RunShellCommandAndGetOutput (options * ShellOptions , command string , args ... string ) (string , error ) {
32- logCommand (options , command , args ... )
33- cmd := exec .Command (command , args ... )
34-
35- cmd .Stdin = os .Stdin
36-
37- setCommandOptions (options , cmd )
38-
39- out , err := cmd .CombinedOutput ()
40- return string (out ), errors .WithStackTrace (err )
36+ out , err := runShellCommand (options , false , command , args ... )
37+ return out .Combined (), err
4138}
4239
43- // Run the specified shell command with the specified arguments. Return its stdout and stderr as a string and also
44- // stream stdout and stderr to the OS stdout/stderr
40+ // Run the specified shell command with the specified arguments. Return its interleaved stdout and stderr as a string
41+ // and also stream stdout and stderr to the OS stdout/stderr
4542func RunShellCommandAndGetAndStreamOutput (options * ShellOptions , command string , args ... string ) (string , error ) {
46- logCommand (options , command , args ... )
47- cmd := exec .Command (command , args ... )
48-
49- setCommandOptions (options , cmd )
50-
51- cmd .Stdin = os .Stdin
52-
53- stdout , err := cmd .StdoutPipe ()
54- if err != nil {
55- return "" , errors .WithStackTrace (err )
56- }
57-
58- stderr , err := cmd .StderrPipe ()
59- if err != nil {
60- return "" , errors .WithStackTrace (err )
61- }
62-
63- if err := cmd .Start (); err != nil {
64- return "" , errors .WithStackTrace (err )
65- }
66-
67- output , err := readStdoutAndStderr (
68- stdout ,
69- true ,
70- stderr ,
71- true ,
72- options ,
73- )
74- if err != nil {
75- return output , err
76- }
77-
78- err = cmd .Wait ()
79- return output , errors .WithStackTrace (err )
43+ out , err := runShellCommand (options , true , command , args ... )
44+ return out .Combined (), err
8045}
8146
8247// Run the specified shell command with the specified arguments. Return its stdout as a string
8348func RunShellCommandAndGetStdout (options * ShellOptions , command string , args ... string ) (string , error ) {
84- logCommand (options , command , args ... )
85- cmd := exec .Command (command , args ... )
86-
87- cmd .Stdin = os .Stdin
88-
89- setCommandOptions (options , cmd )
90-
91- out , err := cmd .Output ()
92- return string (out ), errors .WithStackTrace (err )
49+ out , err := runShellCommand (options , false , command , args ... )
50+ return out .Stdout (), err
9351}
9452
9553// Run the specified shell command with the specified arguments. Return its stdout as a string and also stream stdout
9654// and stderr to the OS stdout/stderr
9755func RunShellCommandAndGetStdoutAndStreamOutput (options * ShellOptions , command string , args ... string ) (string , error ) {
56+ out , err := runShellCommand (options , true , command , args ... )
57+ return out .Stdout (), err
58+ }
59+
60+ // Run the specified shell command with the specified arguments. Return its stdout, stderr, and interleaved output as a
61+ // struct and also stream stdout and stderr to the OS stdout/stderr
62+ func RunShellCommandAndGetOutputStructAndStreamOutput (options * ShellOptions , command string , args ... string ) (* Output , error ) {
63+ return runShellCommand (options , true , command , args ... )
64+ }
65+
66+ // Run the specified shell command with the specified arguments. Return its stdout and stderr as a string and also
67+ // stream stdout and stderr to the OS stdout/stderr
68+ func runShellCommand (options * ShellOptions , streamOutput bool , command string , args ... string ) (* Output , error ) {
9869 logCommand (options , command , args ... )
9970 cmd := exec .Command (command , args ... )
10071
@@ -104,24 +75,23 @@ func RunShellCommandAndGetStdoutAndStreamOutput(options *ShellOptions, command s
10475
10576 stdout , err := cmd .StdoutPipe ()
10677 if err != nil {
107- return "" , errors .WithStackTrace (err )
78+ return nil , errors .WithStackTrace (err )
10879 }
10980
11081 stderr , err := cmd .StderrPipe ()
11182 if err != nil {
112- return "" , errors .WithStackTrace (err )
83+ return nil , errors .WithStackTrace (err )
11384 }
11485
11586 if err := cmd .Start (); err != nil {
116- return "" , errors .WithStackTrace (err )
87+ return nil , errors .WithStackTrace (err )
11788 }
11889
11990 output , err := readStdoutAndStderr (
91+ options .Logger ,
92+ streamOutput ,
12093 stdout ,
121- true ,
12294 stderr ,
123- false ,
124- options ,
12595 )
12696 if err != nil {
12797 return output , err
@@ -131,48 +101,6 @@ func RunShellCommandAndGetStdoutAndStreamOutput(options *ShellOptions, command s
131101 return output , errors .WithStackTrace (err )
132102}
133103
134- // This function captures stdout and stderr while still printing it to the stdout and stderr of this Go program
135- func readStdoutAndStderr (
136- stdout io.ReadCloser ,
137- includeStdout bool ,
138- stderr io.ReadCloser ,
139- includeStderr bool ,
140- options * ShellOptions ,
141- ) (string , error ) {
142- allOutput := []string {}
143-
144- stdoutScanner := bufio .NewScanner (stdout )
145- stderrScanner := bufio .NewScanner (stderr )
146-
147- for {
148- if stdoutScanner .Scan () {
149- text := stdoutScanner .Text ()
150- options .Logger .Println (text )
151- if includeStdout {
152- allOutput = append (allOutput , text )
153- }
154- } else if stderrScanner .Scan () {
155- text := stderrScanner .Text ()
156- options .Logger .Println (text )
157- if includeStderr {
158- allOutput = append (allOutput , text )
159- }
160- } else {
161- break
162- }
163- }
164-
165- if err := stdoutScanner .Err (); err != nil {
166- return "" , errors .WithStackTrace (err )
167- }
168-
169- if err := stderrScanner .Err (); err != nil {
170- return "" , errors .WithStackTrace (err )
171- }
172-
173- return strings .Join (allOutput , "\n " ), nil
174- }
175-
176104func logCommand (options * ShellOptions , command string , args ... string ) {
177105 if options .SensitiveArgs {
178106 options .Logger .Infof ("Running command: %s (args redacted)" , command )
0 commit comments