Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

Goでナイーブなtailを作る

私が日頃作るツールの大抵はフィルタなので、まずはcatコマンドが実装出来ることが必要。 とりあえず、ファイルをいったん全部読み込んで、最後の5行だけ書き出す、ナイーブな実装のtailコマンドを作ってみる。

パッケージのリストを眺めるとio/ioutil.ReadFileが良さそう。

サンプルをそのまま引き写す

package main

import (
    "fmt"
    "io/ioutil"
    "log"
)

func main() {
    content, err := ioutil.ReadFile("./sample.memo")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", content)
}

ReadFile()の戻りは(byte, error)である。 byteとstringの関係を知る必要がある。公式ドキュメントのstring型の説明を読むと、

string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.

てけとーな訳

string型は、8bitのbyteのあらゆる並びの集合です。伝統的には、UTF-8エンコードされたテキストを表現するものですが、 必ずしもそうである必要はありません。string型は空かもしれませんが、nilにはなりません。string型の値は、immutable(不変)です。

という説明なので、UTF-8のシーケンスであれば、byteと大差は無いのだろう。

とりあえず、byteはPrintf()を使えば文字列として表示できることはわかった。 これをPrintln()にすると、255以下の数字が並ぶことになる。 単純に型変換したらどうなるのだろう。fmt.Println(string(content))としたら、これでもちゃんと表示された。よしよし。

さて、これを各行に分解したい。すると、bufio.Scannerというのがあるみたい。 なるほど、いわゆるバッファドIOは、ioじゃなくて、bufioを使うのね。 ファイルを1行ずつ読み込むのはこれで良さそう。

ただし、今回は一挙に全部読み込みたいので、読み込んだものをsplitして各行にしたい。

regexpパッケージにSplitがあるので、たぶんこれでよいだろう。

main関数の中はこんな感じにした。regexpのimportは追加してある。

func main() {
    content, err := ioutil.ReadFile("./sample.memo")
    if err != nil {
        log.Fatal(err)
    }

    re := regexp.MustCompile(`\r?\n`)
    lines := re.Split(string(content), -1)
    noLines := len(lines)
    startidx := noLines - 5
    for i, v := range lines[startidx:] {
        fmt.Println(i+startidx+1, " : ", v)
    }
}

うん。ちゃんとラスト5行が取り出せている。おけ、おけ。