〰️

bufio/NewReaderとos/StdinでCLから入力を取得する

2024/06/09に公開2

GoではPythonのinput()みたいにコマンドライン(CL)から入力を取得するにはどうすればいいのだろうと思っていたので、CLからの入力値を合計する簡単な関数を作ってみます!

CLからの標準I/O

GoではCLからの入出力とエラーをosパッケージのStdinStdoutStderrでそれぞれ以下のように定義されています。

var (
	Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
	Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
	Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

https://pkg.go.dev/os#pkg-variables

今回はCLの入力を扱うので、StdinとエラーハンドリングでStderrを使用します。

入力をメモリに保存するbufio/NewReader

CLから得た入力はio.Readerで値を取得しますが、今回は取得値をメモリに保存するバッファ付きI/Oのbufioパッケージを使用します。

バッファというメモリを介することで、ハードディスクなどに保存するよりも高速にデータの入出力が可能になります。

https://pkg.go.dev/bufio

CLからの入力値を合計するsumNumbers()の実装

まずは必要なパッケージをインポートします。

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

sumNumbers()は以下のように実装してみました。

func sumNumbers() (int, error) {
	fmt.Println("Please enter numbers one by one, followed by Enter. Enter 0 to end input:")
	var sum int
	// 標準入力から数値を読み込んでメモリに格納
	reader := bufio.NewReader(os.Stdin)

	for {
		// 入力された数値を文字列として取得
		input, err := reader.ReadString('\n')
		if err != nil {
			return 0, err
		}
		// 入力された文字列からスペースを削除
        // '\n'がスペースとして残っているため削除しないとのちの`Atoi()`でエラーになる
		input = strings.TrimSpace(input)

		// 0を入力したらループを抜ける
		if input == "0" {
			break
		}

		// 入力された文字列を数値に変換
		num, err := strconv.Atoi(input)
		if err != nil {
			fmt.Printf("Invalid number: %s\n", input)
			continue
		}

		sum += num
	}
	return sum, nil
}

流れとしては以下のような感じです。

  1. CLからの入力値inputを文字列として取得し、メモリに格納する

  2. inputを整形してstrconv.Atoi()int型に変換する

  3. Stdinが0になればそれまでの入力値を合計して返す

main()で実行

main()を以下のように実装して、プログラムを実行します。

func main() {
	sum, err := sumNumbers()
	if err != nil {
		// 標準エラー出力にエラーメッセージを表示し、プログラムを終了
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("Sum: %d\n", sum)
}

実行結果は以下のようになりました。

Please enter numbers one by one, followed by Enter. Enter 0 to end input:
1
2
3
0
Sum: 6

まとめ

今回はbufioパッケージとos/Stdinを使ってCLからの入力を取得してデータ処理する関数を実装しました。

Pythonはこれをinput()だけでできたはずなので、改めてPythonのパッケージの豊富さと便利さに気付かされます。。。

Discussion

tenkohtenkoh

こんにちは。bufioを使うのであれば、bufio.Scannerを使うのが便利だと思います。

sc := bufio.NewScanner(os.Stdin)

// 入力をすべて捌くまで繰り返し読み込み。
// デフォルトでは改行区切り。区切り文字を変えたい場合はsc.Split()を使う。
for sc.Scan() {
    input := sc.Text() // 改行は除去済みの文字列を取得できる
    // 以下省略
}

if sc.Err() != nil {
    // 読み込みが正常にできなかった場合のエラー処理
}
DimuDimu

恥ずかしながら知りませんでした、、便利ですね😮
例のコードまで書いてくださりありがとうございます!!