Goで標準エラー出力に回すとき

4 min read

こんにちは、無能です。
割とここの部分で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で扱う必要がある。
次にfprintの第一引数に標準エラー出力としてのos.Stderrを与えてみよう。

fmt.Printとfmt.Fprintの違い

以下のように変更してみましょう。
この記事がとてもわかりやすいです。
Go の fmt.Print 系関数は上手に使い分けたい
もしこの記事でもなんじゃこれ!となるのならば、C言語のprint系の出力の違いを調べれば納得いくものが見つかるかもしれません。
printfが意味わからなければawkでも試すことができます。

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

awkfprintは・・・。知識不足でわからない・・・。
わかる方いれば教えていただきたいです。

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

正しくエラー出力が行われました。

本当にこの手の扱いってかなり頭から飛びやすいので、特に正しいエラー出力には注意して触らないとなあ…とつくづく感じました。

PGP --- Contact --- Machines