在Go中将输出重定向到标准错误

5 min

language: ja bn en es hi pt ru zh-cn zh-tw

你好,我是无能。
关于Go语言中这部分的说明,我发现资料不多,所以特此记录下来。

检查返回值

最初有这样一段代码时。

func main() {
  if len(os.Args) < 2 {
    fmt.Println("Usage: go run main.go https://soulminingrig.com/")
    return
  }

  url := os.Args[1]
  title, err := fetchTitle(url)
  if err != nil {
    fmt.Printf( "Error: %v\n", err)
    return
  }

  fmt.Printf("Title: %s\n", title)
}

当然,在Go语言中,上述代码中会产生错误的地方也会作为错误输出,但如果我想获取构建后可执行二进制文件的错误返回值,该怎么办呢?

alleycat:[haturatu]:~/git/go-title$ ./go-title 
Usage: go run main.go https://soulminingrig.com/
alleycat:[haturatu]:~/git/go-title$ echo $?
0
alleycat:[haturatu]:~/git/go-title$ ./go-title http://https://soulminingrig.nodomain/
Error: failed to fetch URL: Get "http://https//soulminingrig.nodomain/": dial tcp: lookup https: no such host
alleycat:[haturatu]:~/git/go-title$ echo $?
0

所有内容都变成了标准输出。

使用os.Exit指定返回值

为了使其正确地被视为错误,需要使用os.Exit进行指定,因此修改如下。

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: go run main.go https://soulminingrig.com/")
		os.Exit(1)
	}

	url := os.Args[1]
	title, err := fetchTitle(url)
	if err != nil {
		fmt.Printf( "Error: %v\n", err)
		os.Exit(2)
	}

	fmt.Printf("Title: %s\n", title)
}

现在再次构建并运行。

alleycat:[haturatu]:~/git/go-title$ ./go-title 
Usage: go run main.go https://soulminingrig.com/
alleycat:[haturatu]:~/git/go-title$ echo $?
1
alleycat:[haturatu]:~/git/go-title$ ./go-title http://https://soulminingrig.nodomain/
Error: failed to fetch URL: Get "http://https//soulminingrig.nodomain/": dial tcp: lookup https: no such host
alleycat:[haturatu]:~/git/go-title$ echo $?
2

返回值正确地返回了。

尝试将返回值为0的成功模式丢弃到/dev/null

通过> /dev/null可以只丢弃标准输出中返回值为0的部分,我们来试试看。

alleycat:[haturatu]:~/git/go-title$ ./go-title > /dev/null
alleycat:[haturatu]:~/git/go-title$ ./go-title http://https://soulminingrig.nodomain/ > /dev/null

在这种情况下,fmt.Println属于标准输出,因此无法看到错误消息。
对于大多数正确的GNU Tools或其他可执行二进制文件来说,错误消息通常不会输出到标准输出,因此错误消息也应该输出到错误输出。
即使在官方文档中,也大多是使用fmt.Print进行输出,
Return and handle an error
https://pkg.go.dev/errors
这仅仅是因为在Go语言的世界中创建模块时,通常没有必要特意进行标准错误输出,但这不符合在调试时只想通过> /dev/null查看错误输出的需求。
归根结底,这里需要像C语言一样使用fprint来处理。
接下来,我们尝试将os.Stderr作为fprint的第一个参数,作为标准错误输出。

fmt.Print和fmt.Fprint的区别

让我们进行如下修改。
这篇文章非常易懂。
Go 的 fmt.Print 系列函数应该巧妙地使用
如果即使看了这篇文章你还是觉得困惑,那么你可以查阅C语言中print系列函数的输出差异,可能会找到满意的解释。
如果printf让你感到困惑,你也可以尝试使用awk

$ ls -la | awk '{print $9}'
$ ls -la | awk '{printf $9}'

awk中使用fprint... 我知识不足,不清楚...
如果有知道的朋友,请告诉我。

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintln(os.Stderr, "Usage: go run main.go https://soulminingrig.com/")
		os.Exit(1)
	}

	url := os.Args[1]
	title, err := fetchTitle(url)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(2)
	}

	fmt.Printf("Title: %s\n", title)
}

现在让我们看看结果。

alleycat:[haturatu]:~/git/go-title$ ./go-title > /dev/null
Usage: go run main.go https://soulminingrig.com/
alleycat:[haturatu]:~/git/go-title$ echo $?
1
alleycat:[haturatu]:~/git/go-title$ ./go-title http://https://soulminingrig.nodomain/ > /dev/null
Error: failed to fetch URL: Get "http://https//soulminingrig.nodomain/": dial tcp: lookup https: no such host
alleycat:[haturatu]:~/git/go-title$ echo $?
2

错误输出已正确执行。

这种处理方式真的很容易从脑海中溜走,所以我深切地感觉到,在处理正确的错误输出时,必须特别小心。

Related Posts