Better way to get the exit status of an os/exec command?

2,294 views
Skip to first unread message

Ben Hoyt

unread,
Jul 20, 2018, 9:05:06 AM7/20/18
to golang-nuts
Hi folks,

I struggled to get the exit status (integer) of a command executed with os/exec. I followed the documentation through ExitError and ProcessState, but could only find the ProcessState.Success() boolean. After searching Google+StackOverflow I found you *can* get it, but it requires importing syscall and converting to a syscall type -- pretty klunky.

Here's roughly the code snippet that I'm using:

    err = cmd.Wait()
    if err != nil {
        if exitErr, ok := err.(*exec.ExitError); ok {
            if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
                return status.ExitStatus()
            }
        }
        return -1
    }
    return 0

It's the "import syscall" and the "exitErr.Sys().(syscall.WaitStatus)" bits that get me. Syscall is platform-specific (though from reading SO, this actually works on all platforms), and it's an extra type conversion and if statement that seems unnecessary once you have the ExitError.

Am I missing something, and is there a simpler way?

If not, I propose that we add an ExitStatus() method to os.ProcessState. Then you could just call exitErr.ExitStatus() directly, or even cmd.ProcessState.ExitStatus(). We'd have to document what value it returns if the process hasn't exited (but the same issue is true for ProcessState.Success()).

-Ben

Manlio Perillo

unread,
Jul 20, 2018, 9:20:36 AM7/20/18
to golang-nuts
Il giorno venerdì 20 luglio 2018 15:05:06 UTC+2, Ben Hoyt ha scritto:
Hi folks,

I struggled to get the exit status (integer) of a command executed with os/exec. I followed the documentation through ExitError and ProcessState, but could only find the ProcessState.Success() boolean. After searching Google+StackOverflow I found you *can* get it, but it requires importing syscall and converting to a syscall type -- pretty klunky.

Here's roughly the code snippet that I'm using:
 
> [...] 
 
It's the "import syscall" and the "exitErr.Sys().(syscall.WaitStatus)" bits that get me. Syscall is platform-specific (though from reading SO, this actually works on all platforms), and it's an extra type conversion and if statement that seems unnecessary once you have the ExitError.

Am I missing something, and is there a simpler way?

The reason ExitStatus is not defined is probably because plan9 does not support it.  Instead of a status code it returns a message.
You can look at WaitMsg.ExitStatus implementation in https://golang.org/src/syscall/syscall_plan9.go.


If not, I propose that we add an ExitStatus() method to os.ProcessState. Then you could just call exitErr.ExitStatus() directly, or even cmd.ProcessState.ExitStatus(). We'd have to document what value it returns if the process hasn't exited (but the same issue is true for ProcessState.Success()).


Yes, probably ExitStatus can be implemented directly for ProcessState.  But there should be a reason why it has not been implemented.
Probably because ExitStatus is a low level detail.  You don't usually need it, and if you really need it you will need to use the syscall package for more details.


Manlio

Ben Hoyt

unread,
Jul 20, 2018, 9:30:53 AM7/20/18
to manlio....@gmail.com, golang-nuts
 
The reason ExitStatus is not defined is probably because plan9 does not support it.  Instead of a status code it returns a message. 
You can look at WaitMsg.ExitStatus implementation in https://golang.org/src/syscall/syscall_plan9.go.

I realize some of the Go core devs have a strong connection to plan9, but in reality Linux+macOS+Windows are what people are using. Also, with implementing ExitStatus(), syscall_plan9.go itself is acknowledging it's important enough to fulfill that contract even for plan9 (even when it's defined as a hack to look at the message length and return a 0 or 1 status based on that).

If not, I propose that we add an ExitStatus() method to os.ProcessState. Then you could just call exitErr.ExitStatus() directly, or even cmd.ProcessState.ExitStatus(). We'd have to document what value it returns if the process hasn't exited (but the same issue is true for ProcessState.Success()).


Yes, probably ExitStatus can be implemented directly for ProcessState.  But there should be a reason why it has not been implemented.
Probably because ExitStatus is a low level detail.  You don't usually need it, and if you really need it you will need to use the syscall package for more details.

I disagree. I'm using it, many others have asked on StackOverflow. You might use it to display the status code in a different font or in bold, or in a UI label. You might use it because you're "scripting" with Go, calling sub-processes and switching on the well-defined status codes of a particular program to determine what to do next (eg, 1=file not found, 2=invalid format, 3=other exception).

-Ben

Manlio Perillo

unread,
Jul 20, 2018, 9:42:59 AM7/20/18
to golang-nuts
Il giorno venerdì 20 luglio 2018 15:30:53 UTC+2, Ben Hoyt ha scritto:
 
The reason ExitStatus is not defined is probably because plan9 does not support it.  Instead of a status code it returns a message. 
You can look at WaitMsg.ExitStatus implementation in https://golang.org/src/syscall/syscall_plan9.go.

I realize some of the Go core devs have a strong connection to plan9, but in reality Linux+macOS+Windows are what people are using. Also, with implementing ExitStatus(), syscall_plan9.go itself is acknowledging it's important enough to fulfill that contract even for plan9 (even when it's defined as a hack to look at the message length and return a 0 or 1 status based on that).

If not, I propose that we add an ExitStatus() method to os.ProcessState. Then you could just call exitErr.ExitStatus() directly, or even cmd.ProcessState.ExitStatus(). We'd have to document what value it returns if the process hasn't exited (but the same issue is true for ProcessState.Success()).


Yes, probably ExitStatus can be implemented directly for ProcessState.  But there should be a reason why it has not been implemented.
Probably because ExitStatus is a low level detail.  You don't usually need it, and if you really need it you will need to use the syscall package for more details.

I disagree. I'm using it, many others have asked on StackOverflow. You might use it to display the status code in a different font or in bold, or in a UI label.

Do a normal user needs to see something like: "process exited with status code xxx" ?

You might use it because you're "scripting" with Go, calling sub-processes and switching on the well-defined status codes of a particular program to determine what to do next (eg, 1=file not found, 2=invalid format, 3=other exception).

Ben Hoyt

unread,
Jul 20, 2018, 9:50:18 AM7/20/18
to manlio....@gmail.com, golang-nuts
You might use it because you're "scripting" with Go, calling sub-processes and switching on the well-defined status codes of a particular program to determine what to do next (eg, 1=file not found, 2=invalid format, 3=other exception).

AFAIK, this is not portable:

Fair, but I just meant that for *specific programs* the status codes may be well-defined and documented, not in general. For example, bash (http://tldp.org/LDP/abs/html/exitcodes.html) is documented to return 1 for "general errors" and 2 for "misuse of shell builtins".

-Ben

Ian Lance Taylor

unread,
Jul 20, 2018, 10:27:00 AM7/20/18
to Ben Hoyt, golang-nuts
On Fri, Jul 20, 2018 at 6:05 AM, Ben Hoyt <ben...@gmail.com> wrote:
>
> If not, I propose that we add an ExitStatus() method to os.ProcessState.
> Then you could just call exitErr.ExitStatus() directly, or even
> cmd.ProcessState.ExitStatus(). We'd have to document what value it returns
> if the process hasn't exited (but the same issue is true for
> ProcessState.Success()).

Seems reasonable to me, though you'd have to specify what to do on
Plan 9. See https://golang.org/s/proposal.

Ian

buc...@gmail.com

unread,
Jul 20, 2018, 4:02:15 PM7/20/18
to golang-nuts
Don't know if this is easier, agree no present way is easy enough, but this worked for one project I was working on:

cmd := exec.Command("zfs", "get", "all")
_ = cmd.Run() // shamelessly discard any error
exitcode := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
if exitcode != 0 {
   whatever...
}

Bucky

Manlio Perillo

unread,
Jul 20, 2018, 4:57:07 PM7/20/18
to golang-nuts
This does not work on plan9, where you need to do:
exitcode := cmd.ProcessState.Sys().(syscall.Waitmsg).ExitStatus()


Also, may not work on future supported operating systems.


Manlio 

Ben Hoyt

unread,
Jul 22, 2018, 7:51:01 PM7/22/18
to golang-nuts
Seems reasonable to me, though you'd have to specify what to do on
Plan 9.  See https://golang.org/s/proposal.

Thanks! Proposal submitted: https://github.com/golang/go/issues/26539

-Ben
Reply all
Reply to author
Forward
0 new messages