私が日頃作るツールの大抵はフィルタなので、まずは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行が取り出せている。おけ、おけ。