while( <> ){…} みたいなことを Go で

UNIX風のテキストファイルフィルターを書くのに便利なように Perl には

while( <> ){
   print $_
}

という構文がある。これと同じような手軽さで Go でもフィルターを書くためのライブラリ argf を書いてみた。

package main

import (
    "fmt"
    "os"

    "github.com/zetamatta/experimental/argf"
)

func main() {
    r := argf.New()
    for r.Scan() {
        fmt.Println(r.Text())
    }
    if err := r.Err(); err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
    }
}
  • 基本インターフェイスbufio.Scanner に準拠した(=ほぼ同じメソッド構成)
  • len(os.Args) < 2 の時は、標準入力から読み取るが、そうでない時は os.Args[1:] のファイルを順に読み込んでゆく
  • argf.New()インスタンスには、AWK の組み込み変数と同じ名前のメソッドを実装している
    • NR() … 読み込みレコード数(行数)
    • Filename() … 現在読み込んでいるファイルの名前
    • FNR() … 現在のファイルで読み込んだレコード数(行数)

かんたんな grep もどきも書いてみた。

package main

import (
    "errors"
    "fmt"
    "github.com/zetamatta/experimental/argf"
    "os"
    "regexp"
)

func main1() error {
    if len(os.Args) < 2 {
        return errors.New("Usage: grep.exe REGEXP Files...")
    }
    rx, err := regexp.Compile(os.Args[1])
    if err != nil {
        return err
    }
    r := argf.NewFiles(os.Args[2:])
    for r.Scan() {
        if rx.MatchString(r.Text()) {
            fmt.Printf("%s(%d): %s\n", r.Filename(), r.FNR(), r.Text())
        }
    }
    return r.Err()
}

func main() {
    if err := main1(); err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
        os.Exit(1)
    }
    os.Exit(0)
}

argf.New()os.Args[1:] を全て読み込み対象としてしまうが、grep もどきでは os.Args[1] は検索文字列となるので、読み込み対象から除きたい。そのために argf.NewFiles(args []string) という、読み込み対象のファイル名を、引数で与えるタイプのコンストラクタも用意した。

今週の zetamatta/experimental は以上です。