获取退出代码 – Go

huangapple go评论87阅读模式
英文:

Get exit code - Go

问题

我正在使用包os/exec http://golang.org/pkg/os/exec/来在操作系统中执行命令,但是我似乎找不到获取退出码的方法。虽然我可以读取输出。

即:

package main

import(
    "os/exec"
    "bytes"
    "fmt"
    "log"
    )

func main() {
    cmd := exec.Command("somecommand", "parameter")
    var out bytes.Buffer
    cmd.Stdout = &out
    if err := cmd.Run() ; err != nil {
        //log.Fatal( cmd.ProcessState.Success() )
        log.Fatal( err )
    }
    fmt.Printf("%q\n", out.String() )
}
英文:

I'm using the package: os/exec http://golang.org/pkg/os/exec/ to execute a command in the operating system but I don't seem to find the way to get the exit code. I can read the output though

ie.

package main

import(
    "os/exec"
    "bytes"
    "fmt"
    "log"
    )

func main() {
    cmd := exec.Command("somecommand", "parameter")
    var out bytes.Buffer
    cmd.Stdout = &out
    if err := cmd.Run() ; err != nil {
        //log.Fatal( cmd.ProcessState.Success() )
        log.Fatal( err )
    }
    fmt.Printf("%q\n", out.String() )
}

答案1

得分: 112

很容易确定退出代码是0还是其他值。在第一种情况下,cmd.Wait()将返回nil(除非在设置管道时发生其他错误)。

不幸的是,没有平台无关的方法来获取错误情况下的退出代码。这也是为什么它不是API的一部分的原因。以下代码片段适用于Linux,但我没有在其他平台上进行测试:

package main

import "os/exec"
import "log"
import "syscall"

func main() {
    cmd := exec.Command("git", "blub")

    if err := cmd.Start(); err != nil {
        log.Fatalf("cmd.Start: %v", err)
    }

    if err := cmd.Wait(); err != nil {
        if exiterr, ok := err.(*exec.ExitError); ok {
            log.Printf("Exit Status: %d", exiterr.ExitCode())
        } else {
            log.Fatalf("cmd.Wait: %v", err)
        }
    }
}

只需follow the api docs 了解更多信息 获取退出代码 – Go

英文:

It's easy to determine if the exit code was 0 or something else. In the first case, cmd.Wait() will return nil (unless there is another error while setting up the pipes).

Unfortunately, there is no platform independent way to get the exit code in the error case. That's also the reason why it isn't part of the API. The following snippet will work with Linux, but I haven't tested it on other platforms:

package main

import "os/exec"
import "log"
import "syscall"

func main() {
    cmd := exec.Command("git", "blub")

    if err := cmd.Start(); err != nil {
        log.Fatalf("cmd.Start: %v", err)
    }

    if err := cmd.Wait(); err != nil {
        if exiterr, ok := err.(*exec.ExitError); ok {
            log.Printf("Exit Status: %d", exiterr.ExitCode())
        } else {
            log.Fatalf("cmd.Wait: %v", err)
        }
    }
}

Just follow the api docs to find out more 获取退出代码 – Go

答案2

得分: 96

自从golang版本1.12以来,退出代码可以以本地和跨平台的方式使用。参见ExitErrorExitCode()

> ExitCode返回已退出进程的退出代码,如果进程尚未退出或被信号终止,则返回-1。

if err := cmd.Run() ; err != nil {
	if exitError, ok := err.(*exec.ExitError); ok {
		return exitError.ExitCode()
	}
}
英文:

Since golang version 1.12, the exit code is available natively and in a cross-platform manner. See ExitError and ExitCode().

> ExitCode returns the exit code of the exited process, or -1 if the process hasn't exited or was terminated by a signal.

if err := cmd.Run() ; err != nil {
	if exitError, ok := err.(*exec.ExitError); ok {
		return exitError.ExitCode()
	}
}

答案3

得分: 27

这是基于@tux21b的答案的增强版本

utils/cmd.go

package utils

import (
	"bytes"
	"log"
	"os/exec"
	"syscall"
)

const defaultFailedCode = 1

func RunCommand(name string, args ...string) (stdout string, stderr string, exitCode int) {
	log.Println("运行命令:", name, args)
	var outbuf, errbuf bytes.Buffer
	cmd := exec.Command(name, args...)
	cmd.Stdout = &outbuf
	cmd.Stderr = &errbuf

	err := cmd.Run()
	stdout = outbuf.String()
	stderr = errbuf.String()

	if err != nil {
		// 尝试获取退出码
		if exitError, ok := err.(*exec.ExitError); ok {
			ws := exitError.Sys().(syscall.WaitStatus)
			exitCode = ws.ExitStatus()
		} else {
			// 如果`name`在$PATH中不可用,将发生这种情况(在OSX中),
			// 在这种情况下,无法获取退出码,并且stderr很可能为空字符串,
			// 因此我们使用默认的失败代码,并将err格式化为字符串并设置为stderr
			log.Printf("无法获取失败程序的退出码: %v, %v", name, args)
			exitCode = defaultFailedCode
			if stderr == "" {
				stderr = err.Error()
			}
		}
	} else {
		// 成功,如果go正常,则exitCode应为0
		ws := cmd.ProcessState.Sys().(syscall.WaitStatus)
		exitCode = ws.ExitStatus()
	}
	log.Printf("命令结果, stdout: %v, stderr: %v, exitCode: %v", stdout, stderr, exitCode)
	return
}

我已在OSX上进行了测试,如果在其他平台上无法按预期工作,请告诉我,以便我们可以改进它。

英文:

Here's my enhanced version based on @tux21b 's answer

utils/cmd.go

package utils

import (
	"bytes"
	"log"
	"os/exec"
	"syscall"
)

const defaultFailedCode = 1

func RunCommand(name string, args ...string) (stdout string, stderr string, exitCode int) {
	log.Println("run command:", name, args)
	var outbuf, errbuf bytes.Buffer
	cmd := exec.Command(name, args...)
	cmd.Stdout = &outbuf
	cmd.Stderr = &errbuf

	err := cmd.Run()
	stdout = outbuf.String()
	stderr = errbuf.String()

	if err != nil {
		// try to get the exit code
		if exitError, ok := err.(*exec.ExitError); ok {
			ws := exitError.Sys().(syscall.WaitStatus)
			exitCode = ws.ExitStatus()
		} else {
			// This will happen (in OSX) if `name` is not available in $PATH,
			// in this situation, exit code could not be get, and stderr will be
			// empty string very likely, so we use the default fail code, and format err
			// to string and set to stderr
			log.Printf("Could not get exit code for failed program: %v, %v", name, args)
			exitCode = defaultFailedCode
			if stderr == "" {
				stderr = err.Error()
			}
		}
	} else {
		// success, exitCode should be 0 if go is ok
		ws := cmd.ProcessState.Sys().(syscall.WaitStatus)
		exitCode = ws.ExitStatus()
	}
	log.Printf("command result, stdout: %v, stderr: %v, exitCode: %v", stdout, stderr, exitCode)
	return
}

I have tested it on OSX, if it's not working as expected on other platforms, please tell me so we can make it better.

答案4

得分: 16

September 2019, Go 1.13引入了errors.As,它支持错误的“解包” - 用于在嵌套的调用链中查找精确的错误。

因此,要提取和检查运行外部命令时的两个最常见错误:


err := cmd.Run()

var (
    ee *exec.ExitError
    pe *os.PathError
)

if errors.As(err, &ee) {
    log.Println("exit code error:", ee.ExitCode()) // 运行了,但是退出码非零

} else if errors.As(err, &pe) {
    log.Printf("os.PathError: %v", pe) // "没有这样的文件 ...", "权限被拒绝"等

} else if err != nil {
    log.Printf("general error: %v", err) // 发生了一些非常糟糕的事情!

} else {
    log.Println("success!") // 无错误运行(退出码为零)
}
英文:

September 2019, Go 1.13 introduced errors.As which supports error "unwrapping" - handy for finding precise errors in a nested call-chain.

So to extract and inspect the two most common errors when running an external command:


err := cmd.Run()

var (
    ee *exec.ExitError
    pe *os.PathError
)

if errors.As(err, &ee) {
    log.Println("exit code error:", ee.ExitCode()) // ran, but non-zero exit code

} else if errors.As(err, &pe) {
    log.Printf("os.PathError: %v", pe) // "no such file ...", "permission denied" etc.

} else if err != nil {
    log.Printf("general error: %v", err) // something really bad happened!

} else {
    log.Println("success!") // ran without error (exit code zero)
}

答案5

得分: 2

最近在开发我的辅助包时遇到了这个问题。

根据这里的示例代码:https://go-review.googlesource.com/c/go/+/213337/1/src/os/exec/example_test.go

func ExampleExitError() {
	cmd := exec.Command("sleep", "-u")
	err := cmd.Run()
	var exerr *exec.ExitError
	if errors.As(err, &exerr) {
		fmt.Printf("the command exited unsuccessfully: %d\n", exerr.ExitCode())
}

退出码

我为自己的exec cmd包装器做了以下操作:

// 返回退出码
func (self *MyCmd) ExitCode() int {
	var exitErr *exec.ExitError
	if errors.As(self.Err, &exitErr) {
		return exitErr.ExitCode()
	}
	// 没有错误
	return 0
}

self.Err是从exec.Command.Run()返回的值。完整的列表在这里:https://github.com/J-Siu/go-helper/blob/master/myCmd.go

文本错误消息

虽然@colm.anseo的答案考虑了os.patherror,但它没有给出错误码(int),而且在我看来应该单独处理。相反,文本错误消息可以从execCmd.Stderr中提取,如下所示:

func (self *MyCmd) Run() error {
	execCmd := exec.Command(self.CmdName, *self.ArgsP...)
	execCmd.Stdout = &self.Stdout
	execCmd.Stderr = &self.Stderr
	execCmd.Dir = self.WorkDir
	self.CmdLn = execCmd.String()
	self.Err = execCmd.Run()
	self.Ran = true
	ReportDebug(&self, "myCmd:", false, false)
	ReportDebug(self.Stderr.String(), "myCmd:Stderr", false, false)
	ReportDebug(self.Stdout.String(), "myCmd:Stdout", false, false)
	return self.Err
}

self.Stderr是一个bytes.Buffer,在Run()之前传递给execCmd。在execCmd.Run()之后,可以使用self.Stderr.String()提取文本错误。

英文:

Recently run into this when developing my helper package.

Base on example code here: https://go-review.googlesource.com/c/go/+/213337/1/src/os/exec/example_test.go

func ExampleExitError() {
	cmd := exec.Command("sleep", "-u")
	err := cmd.Run()
	var exerr *exec.ExitError
	if errors.As(err, &exerr) {
		fmt.Printf("the command exited unsuccessfully: %d\n", exerr.ExitCode())
}

The Exit Code

I end up doing the following for my own exec cmd wrapper:

// Return exit code
func (self *MyCmd) ExitCode() int {
	var exitErr *exec.ExitError
	if errors.As(self.Err, &exitErr) {
		return exitErr.ExitCode()
	}
	// No error
	return 0
}

self.Err is the return value from a exec.Command.Run(). Complete listing is here: https://github.com/J-Siu/go-helper/blob/master/myCmd.go

Text Error Message

While @colm.anseo answer take consideration of os.patherror, it doesn't give an error code(int), and IMHO should be handled separately. Instead text error message can be extract from execCmd.Stderr like follow:

func (self *MyCmd) Run() error {
	execCmd := exec.Command(self.CmdName, *self.ArgsP...)
	execCmd.Stdout = &self.Stdout
	execCmd.Stderr = &self.Stderr
	execCmd.Dir = self.WorkDir
	self.CmdLn = execCmd.String()
	self.Err = execCmd.Run()
	self.Ran = true
	ReportDebug(&self, "myCmd:", false, false)
	ReportDebug(self.Stderr.String(), "myCmd:Stderr", false, false)
	ReportDebug(self.Stdout.String(), "myCmd:Stdout", false, false)
	return self.Err
}

self.Stderr is a bytes.Buffer and pass into execCmd before Run(). After execCmd.Run(), text err can be extracted with self.Stderr.String().

答案6

得分: 0

新的包github.com/bitfield/script使得执行命令变得更加容易,并且还有一些非常棒的附加功能。快去看看吧。

在这个例子中,我运行了两个命令。一个出错了,一个没有。两个命令都有输出,并且都显示了退出值。

输出:

运行 git blub
退出状态:1
git: 'blub' 不是一个 git 命令。查看 'git --help'。

最相似的命令是
	pull

--
运行 git version
退出状态:0
git 版本 2.24.3 (Apple Git-128)

--
英文:

New package github.com/bitfield/script makes exec a LOT easier and has some really great added features to it as well. Check it out.

In this example I run two commands. One that errors and one that doesn't. Both have output and both show the exit value.

package main

import (
	"fmt"

	"github.com/bitfield/script"
)

func main() {
	for _, c := range []string{"git blub", "git version"} {
		fmt.Println("running", c)
		p := script.Exec(c)
		fmt.Println("Exit Status:", p.ExitStatus())
		if err := p.Error(); err != nil {
			p.SetError(nil)
			out,_:=p.Stdout()
			fmt.Println(out)
		} else {
			out,_:=p.Stdout()
			fmt.Println(out)
		}
		fmt.Println("--")
	}
}

Output:

running git blub
Exit Status: 1
git: 'blub' is not a git command. See 'git --help'.

The most similar command is
	pull

--
running git version
Exit Status: 0
git version 2.24.3 (Apple Git-128)

--

huangapple
  • 本文由 发表于 2012年4月30日 22:42:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/10385551.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定