当退出代码不为零时,Golang exec.Command返回nil错误

I'm trying to run a command in Golang, but it looks like it loses the exit code because the err is nil:

func runCommand() []byte, error {
  cmd := exec.Command("/bin/bash", "-c", "KUBECONFIG=/tmp/.kube/config helm version")

  cmd.Stdin = os.Stdin
  cmd.Stderr = os.Stderr

  stdOut, err := cmd.StdoutPipe()
  if err != nil {
      return nil, err
  }

  if err := cmd.Start(); err != nil {
      return nil, err
  }

  bytes, err := ioutil.ReadAll(stdOut)
  if err != nil {
      return nil, err
  }
  if err := cmd.Wait(); err != nil {
      return nil, err
  }

  fmt.Println(string(bytes))

  return bytes, nil
}

This return nil, even though the command returns with exit code != 0.

If I type:

$> /bin/bash -c KUBECONFIG=/tmp/.kube/config helm version
$<
$> echo $?
$< 0

If I type:

$> /bin/bash -c 'KUBECONFIG=/tmp/.kube/config helm version'
$< ...connection refused
$> echo $?
$< 1

So I tried to wrap the command in single quote:

cmd := exec.Command("/bin/bash", "-c", "'KUBECONFIG=/tmp/.kube/config helm version'")

but then I get:

/bin/bash: KUBECONFIG=/tmp/.kube/config helm version: No such file or directory

(needless to say that /tmp/.kube/config is there, but I don't think the no such file or directory refers to that anyway).

What am I doing wrong?

Thanks

UPDATE: turns out I got it wrong. In fact I had two commands attempted and for some reason I was sure the one failing was the one I mentioned above, when instead the second command was exiting with a status code different from 0. The code works as expected and the err is not nil in case of exit code != 0. Sorry about that.

Seems like you should be able to get it with exec.ExitError, see exec package. Note that you may need Go 1.12. Here's a runnable example (but it won't give you realistic output at the go playground):

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "os/exec"
)

func main() {
    cmd := exec.Command(`/bin/bash`, `-c`, `FOO=bar ls /foo`)
    cmd.Stdin = os.Stdin
    cmd.Stderr = os.Stderr
    stdOut, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Println("Error 1")
    }
    if err := cmd.Start(); err != nil {
        fmt.Println("Error 2")
    }
    bytes, err := ioutil.ReadAll(stdOut)
    if err != nil {
        fmt.Println("Error 3")
    }
    if err := cmd.Wait(); err != nil {
        fmt.Println("Error 4")
        if exitError, ok := err.(*exec.ExitError); ok {
            fmt.Printf("Exit code is %d
", exitError.ExitCode())
        }
    }
    fmt.Println(string(bytes))
}

On my system that prints:

$ go run main.go
ls: cannot access '/foo': No such file or directory
Error 4
Exit code is 2

If that doesn't work for you, maybe it's worth following @JimB's suggestion and invoking helm directly? The Go standard library should support pipes as well:

It wraps os.StartProcess to make it easier to remap stdin and stdout, connect I/O with pipes, and do other adjustments.

(exec package)