標準入力から改行やスペース区切りの文字を受け取るにはどうすればよいのか。
現状Ruby書きなので、Goで書こうとすると全然ピンとこない。 例えばRubyなら、このように書くだけで標準入力を出力できたので、どうしたらいいんだろうと困惑した。*1
# sample.rb input = gets.to_i puts input # % ruby sample.rb 1 1
3行来るとわかっているならこれでも可能。
# sample.rb input = [] 3.times do input << gets.to_i end p input # % ruby sample.rb 1 2 3 [1, 2, 3]
標準入力
調べると、だいたいこのあたりがヒットした。今回はこの2つについてメモする。
- fmt.Scan()
- bufio.NewScanner()
fmt.Scan()
fmt.Scan()に変数を渡すのはわかるのだが、&a
を渡すことになり困惑した。
普段の癖で、直感的にfmt.Scanにはa
を渡しそうになるが、そうは行かなかった。
package main import "fmt" func main() { var a int fmt.Scan(&a) fmt.Println(a) }
fmt.Scanはポインタを利用して標準入力を受け取るということで、fmt.Scan(&a)とする必要があった。
しかし、上のドキュメントではポインタに関すること特に言及してないので、そのドキュメントを見ても理解できないような気がしている。
……と思ったら、ドキュメントの上の方にちゃんと書かれていた。
スキャンされるすべての引数は,基本型へのポインタまたは Scanner インターフェースの実装のいずれかでなければなりません。
なるほど完全に理解した。
このfmt.Scan()ではスペースで自動で区切って変数に格納できるので、Hello world
という入力を受け付ける場合、a, bの2つの変数を用意するとそれぞれに格納してくれるトノコト。
package main import "fmt" func main() { var a, b string fmt.Scan(&a, &b) fmt.Println(a, b) }
最後はおまけで標準出力させた。
bufio.NewScanner()
bufioパッケージを使った標準入力。
package main import ( "bufio" "fmt" "os" ) // Ctl+Cするまで無限入力状態 func main() { sc := bufio.NewScanner(os.Stdin) for sc.Scan() { fmt.Println(sc.Text()) } }
bufio.NewScanner(os.Stdin)と書いたが、引数はos.Stdinである必要はなく、io.Readerが渡されれば良いとドキュメントには書いてある。
func NewScanner(r io.Reader) *Scanner
NewScanner は, r から読み込む新しい Scanner を返します。
io.Readerの定義は以下のようになっていて、つまりNewScannerで受け取ったos.StdinがReadを持っていれば読み込んでくれるという事だ。
type Reader interface { Read(p []byte) (n int, err error) }
ではos.Stdinのドキュメントを見てみる。
var ( Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin") Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout") Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr") )
Stdin, Stdout ,および Stderr は,標準入力,標準出力,および標準エラーのファイル記述子を指す開いたファイルです。
NewFileの定義は以下。渡されたuintptr(syscall.Stdin)
がFileとして返ってくる。
func NewFile(fd uintptr, name string) *File
NewFile は与えられたファイルディスクリプタと名前で新しい File を返します。
で、もちろんFile型にはReadがあるので、os.StdinすることでNewScannerの引数にos.Stdinを渡しても問題ないということがわかる。
func (f *File) Read(b []byte) (n int, err error)
こうしてbufio.NewScanner(os.Stdin)
によって、標準入力で受け取った値を読み込める。
io.Readerが汎用的に読み込めるおかげであまり意識せずに読み込みをさせることができるようだ。これがインターフェースか……。
で、最後に、宣言したscでScan()とText()を使うことで標準入力にアクセスしてみた。
func main() { sc := bufio.NewScanner(os.Stdin) for sc.Scan() { fmt.Println(sc.Text()) } }
Scan は Scanner で次のトークンまで処理し, そのトークンは Bytes あるいは Text メソッドで取得可能になります。
Text は,Scan 呼び出しで最後に生成されたトークンを返します。 このトークンのバイトを持つ,新たにメモリを割り当てられた文字列を返します。
最後はおまけで標準出力させた。
参考
文中以外に参考にした記事。
もっと詳しい話
より詳細な指摘があればコメントお待ちしてます。
*1:Rubyの標準入力についてそんなに深くまで探ってないだけかもしれない