-3

I want to be able to seamlessly print the output from an unknown(user defined) command in go passed through a io.ReadCloser. The bufio.NewScanner reads the std out and prints the text correctly, however the color that the child process prints is not recorded and passed through the pipe(or I don't know how to access it).

I tried using execErr := syscall.Exec(binary, cmd.Args, os.Environ()) however since this takes over the go process, I can't get an array of processes to run.

// SpawnGroup spawns a group of processes
func SpawnGroup(cmds []*exec.Cmd) {
    spawnWg := &sync.WaitGroup{}
    spawnWg.Add(len(cmds))
    defer spawnWg.Wait()
    for _, cmd := range cmds {
        go Spawn(cmd, spawnWg)
    }
}

// Spawn spawn a child process
func Spawn(cmd *exec.Cmd, wg *sync.WaitGroup) {
    defer wg.Done()
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    err := cmd.Start()
    if err != nil {
        color.Red(err.Error())
    }
    scanner := bufio.NewScanner(stdout)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    errScanner := bufio.NewScanner(stderr)
    for errScanner.Scan() {
        color.Red(scanner.Text())
    }
    cmd.Wait()
}

For example, if I try to run two commands passed in an array such as sls offline and vue-cli-service serve everything works as expected, but the logged output doesn't have the color. Both of these processes print some of their output to the command line with color. The exact array of commands will be unknown, so I need a way to print the exact output from them.

I was able to get this to work in node by using:

  let cmd = await spawn(command, args, {
    stdio: 'inherit',
    shell: true,
    ...options
  });

I haven't been able to track down how to do this in go.

Thanks for any advice or help, this is my first real dive into go, but it seems like an excellent language!

Tim
  • 80
  • 3
  • See https://stackoverflow.com/questions/45117214/golang-os-exec-stdoutpipe-with-colors – Charlie Tumahai Sep 22 '19 at 16:37
  • 2
    These commands might not generate "colored output" if not attached to a terminal. – Volker Sep 22 '19 at 18:05
  • 1
    There's nothing to be done in Go. Go doesn't arbitrarily "remove color". If you don't see colored output, it's because the processes turned it off. Like Volker said, that's normal behavior if stdout isn't a terminal (run `sls offline | cat` and you'll see uncolored output too). Assign os.Stdout to the commands' Stdout field to connect them directly to your terminal. There may also be a flag that forces colors to be turned on. – Peter Sep 22 '19 at 20:47
  • I did take a look at the minecraft post. stackoverflow.com/questions/45117214/ That doesn't help as the command is unknown at run time. @Peter - I am not claiming that "go arbitrarily removes color" I am asking how to pass the color of an known process through go's io.ReadCloser, or how to pass the hex color from a process. – Tim Sep 23 '19 at 15:14
  • @Peter again, the command that go executes is dynamic and user defined. It could be a custom script - the go app can't possibly know what flag to use to force that and I would prefer not to force the user to pass some extra flag just to get my orchestration utility to work. Can I assign a multitude of commands to a single os.Stdout? – Tim Sep 23 '19 at 15:27
  • "the color that the child process prints is not recorded and passed through the pipe" This implies that Go removes the color but it doesn't. Yes, you can assign os.Stdout to multiple commands, but the output will most likely be interleaved in unintended ways. I wasn't suggesting to have the Go program add any flags. I meant to suggest to have the caller of your Go program do so if they want colored output. – Peter Sep 23 '19 at 16:12
  • No worries, man, assigning the os.Stdout was what I was looking to do. – Tim Sep 23 '19 at 16:30

1 Answers1

1

Changing the spawn code to assign the os.Stdout to the cmd.Stdout enabled the output to print with the correct colors.

func Spawn(cmd *exec.Cmd, wg *sync.WaitGroup) {
    defer wg.Done()
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    err := cmd.Start()
    if err != nil {
        color.Red(err.Error())
    }
    defer cmd.Wait()
}```
Tim
  • 80
  • 3
  • There's more to be said about why this works. Many command line programs check if stdout is a terminal or not, and issue different output, such as colors or escape codes if they're attached to a terminal. What this solution appears to do is reuse the existing stdout, so commands operate as they would if run from the same terminal. It might not work in all cases, but it's an extremely simple and useful solution. Thanks, Tim. – CodeGuy2001 Dec 28 '20 at 18:03