0

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.

4
  • 3
    The command you’re running is bash, and that is exiting with 0. Why not run helm directly? Commented Jul 14, 2019 at 21:35
  • JimB, the reason was so that I didn't have to split all the arguments in the command, and also I have some commands with pipes that I wouldn't know how to execute in Go. If I use the quotes in bash to run that bash -c command, the exit status is carried over. How can I achieve that from Go? Commented Jul 14, 2019 at 21:58
  • 1
    Looking at this more closely, you're not checking the error from the command, so how do you know it's not returning a non-zero exit status? Is the question actually about setting environment variables? It's much easier to just directly set them in the execution environment rather than dealing with shell parsing (and again, it's much easier to control what's happening if you don't shell out to bash -c at all) Commented Jul 15, 2019 at 13:01
  • Thanks for the comments. I've updated the original question. That was indeed a non-problem. The code return err != nil if the command terminates with exit status != 0. I had two commands running one after the other and got confused on which one was supposed to return err != nil. Sorry Commented Jul 16, 2019 at 7:17

1 Answer 1

2

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\n", 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)

Sign up to request clarification or add additional context in comments.

1 Comment

Updated the question to not being an actual problem, but accepting this answer since you provided code to check exit code. Thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.