在Go语言中如何以错误代码退出程序?

94

如何以惯用方式退出具有某些错误代码的程序?

Exit文档说:“程序立即终止;不运行延迟函数。”,而log.Fatal只是调用Exit。对于不是致命错误的情况,终止程序而不运行延迟函数似乎是极端的。

我应该传递一些指示发生错误的状态,然后在某个我知道可以安全退出并且所有延迟函数已经运行的点上调用Exit(1)吗?


1
怎么样使用一个默认为“clean”的全局变量状态,并在非致命错误时将其设置为“dirty”呢?在你的main()退出之前,你可以检查该变量。这并不是完美的解决方案,但在某些情况下可能是最简单的解决方案。(我很高兴评论不能被踩:)) - topskip
1
是的,基本上这就是我最终所做的。我觉得这种方法不够优雅,因为我必须避免在主函数中延迟任何操作(因为我仍然调用Exit(1)来设置返回代码,并且不想杀死我的延迟函数),所以我把原来的主函数(只有三行,其中一行是defer)放到了一个函数中。我希望有人能提供更好的方法。到目前为止,有一个人回复了os.Exit,但当我在评论中引用了os.Exit文档时,他删除了回复,现在又有另一个答案指向了os.Exit。 - dan
6个回答

123

在我的大多数真正的main包中都会按照这些方式进行操作,以便尽早采用return err惯例,并具有适当的终止:

func main() {
    if err := run(); err != nil {
        fmt.Fprintf(os.Stderr, "error: %v\n", err)
        os.Exit(1)
    }
}

func run() error {
    err := something()
    if err != nil {
        return err
    }
    // etc
}

这段代码最好的测试方法是什么? - sprut
在功能测试中运行二进制文件。 - Gustavo Niemeyer
1
使用下划线 _ 来忽略 fmt.Fprintf(…) 返回的错误。 - emlai
以上内容对Linux和DOS都适用吗? - undefined

11

我在Python中常用一种模式,在转换为Go语言时,它看起来像这样:

func run() int {
    // here goes
    // the code

    return 1
}

func main() {
    os.Exit(run())
}

我认为这是最好的解决方案。你可以从run()方法返回一个错误,并让main()方法自行处理。 - Loupax

6

我认为最清晰的方法是在main函数的顶部设置exitCode,然后将关闭操作作为下一步使用defer。这样可以在main函数中的任何位置更改exitCode,并且其最后一个值将被用于退出:

package main

import (
    "fmt"
    "os"
)

func main() {
    exitCode := 0
    defer func() { os.Exit(exitCode) }()

    // Do whatever, including deferring more functions

    defer func() {
        fmt.Printf("Do some cleanup\n")
    }()

    func() {
        fmt.Printf("Do some work\n")
    }()

    // But let's say something went wrong
    exitCode = 1

    // Do even more work/cleanup if you want

    // At the end, os.Exit will be called with the last value of exitCode
}

输出:

Do some work
Do some cleanup

Program exited: status 1.

Go Playgroundhttps://play.golang.org/p/AMUR4m_A9Dw

请注意,这种方法的一个重要缺点是在设置错误代码后,您不能立即退出进程。


保留此内容,但在编写更多的Go代码后,我认为https://dev59.com/FmMk5IYBdhLWcg3wyw7H#18969976(Gustavo Niemeyer的答案)在基本所有情况下都更清晰、更易于使用。跟踪函数调用比跟踪延迟更容易。 - Ben
1
这种方法的缺点是,如果您的代码出现 panic,它将永远不会被打印出来,因为在 panic 传播到顶部之前,您已经在 defer 中退出了程序。 - Mitar

3
如fas所提到的,你可以从os包中使用func Exit(exitcode int)
但是,如果你需要应用延迟函数,你总是可以像这样使用defer关键字: http://play.golang.org/p/U-hAS88Ug4 你执行所有操作,影响一个错误变量,在一切都被清理干净时,你可以安全地退出。
否则,你还可以使用panic/recover: http://play.golang.org/p/903e76GnQ- 当你遇到错误时,你会发送panic信号,在捕获它处理完毕之后结束清理工作。

我认为我理解了你在第一种方法中的意思,但是例子对我来说有点混乱。为什么要延迟执行fct1()和fct2()?这意味着它们将按相反的顺序执行!看起来你想要的是类似于这样的东西,或者不是吗?http://play.golang.org/p/02aNIyQ7K- - marczoid

1

3
我非常确定@dan知道这件事。 - topskip
也许是我看错了或者他修改了,但我认为他在谈论其他事情。 - d1str0
1
我认为这是一个很好的答案,因为它非常简单明了 :) - Jardel Lucca

0

我遵循的另一种好方法是:

if err != nil {
    // log.Fatal will print the error message and will internally call System.exit(1) so the program will terminate
    log.Fatal("fatal error message")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接