シェル/SED/AWK/RUBYスクリプトで書かれたテキスト処理を Go言語で書き直す


Linux で動いているテキスト処理を Windows に移植して WEB API として動かしたいという話があった。

基本的には、テキスト処理で、シェル、sed、awk、ruby を駆使して、入力テキストを何か別の表現形式に変換するものだが、シェル/sed/awk/ruby を用意して連携させるのもめんどうだと思い(msys を使えばいいのだが)、Go 言語で書き直す提案をした。

テキスト処理は、sed、awk、ruby(もしくは、perl、python) といったスクリプト言語の得意とするところであろうが、go 言語ではどうだろうか。

行の処理

典型的な処理は、1行読み出して、行の各フィールドに対して処理をする、といったものだろう。

1行ずつ処理するのに使えるものとしては、bufio.NewScanner だろう。例は、bufioのExample を見ていただきたい。私は、以下のようにして使った。(s は string)

	scanner := bufio.NewScanner(strings.NewReader(s))
	for scanner.Scan() {
		line := scanner.Text()
		// 置換処理
 	}

行の各フィールドの処理には、strings.Fields が使えるだろう。例は、strings.FieldsのExampleを見ていただきたい。文字列を渡すと各フィールドが配列として返ってくるので、awk の $1、$2 並に簡単に使える。

置換処理

さて、各行や各フィールドの処理だが、置換など文字列操作を行うことが多いのではないだろうか。

例えば、sed の y/string1/string2/、s/re/replacement/flag、awk の sub、gsub、ruby の tr、sub、gsub など。

置換処理としては、次のようなものが使える。

  • strings.Replace / strings.ReplaceAll
  • strings.Replacer (strings.NewReplacer)
  • regexp

strings.Replace / strings.ReplaceAll や、regexp は他にも解説があるだろうから割愛する。

Replacer は非常にユニークだと思う。例は、NewReplacerのExampleを見ていただきたい。strings.NewReplacer(old, new, old, new,….) のようにして Replacer を作って、Replace 関数で複数のパターン(正規表現ではなく文字列だが)を置換することができる。私は、ruby の tr 関数の置き換えなどに使った。

外部コマンドの実行

このようにして処理した文字列を別のコマンドに引き渡す必要があったのだが、次のようなものが使えた。

実行するコマンドが標準入力からの文字列を受け付けるのであれば、Example にあるように、cmd.Stdin に渡せばよいだろう。

ファイルで渡す必要があれば、ioutil.TempFile でテンポラリーファイルを作って、文字列を ioutil.WriteFile で書き込み、Command の引数にテンポラリファイル名を渡すなどとしてやればよいだろう。

AWK の配列(連想配列)の置き換え

awk スクリプトを置き換えようとしたのだが、配列を使ったやや複雑なスクリプトだった。

アルゴリズムを解読して綺麗に書き直すべきかとは思ったが、時間がかかるので、文法的に置き換えるに留めた。

awk の配列は連想配列であり、添字の部分は、文字列でも数字でもよく、複数指定することができる。例えば、arr[i, j, k]のような形式だ。

map で置き換えることにしたが、map の key に、次のような Tuple とか Triple といった struct を渡すことにした。(java の commons.lang3.tuple を使って同じようなことをした経験からだが、もっと洗練された方法があるかもしれない。名前は Pair と Triple にすべきだったか。)

type Tuple struct {
	Left interface{}
	Right interface{}
}

type Triple struct {
	Left interface{}
	Middle interface{}
	Right interface{}
}

を定義し、

nanika := make(map[Tuple]string)

のようにして使う感じだ。例としては次のようになるだろうか。

import "fmt"

type Tuple struct {
	Left interface{}
	Right interface{}
}

type Triple struct {
	Left interface{}
	Middle interface{}
	Right interface{}
}

func main() {
    nanika := make(map[Tuple]string)

    x := Tuple{1,1}
    nanika[x] = "go"

    fmt.Print(nanika)
}

感想

go ではテキスト処理をするのに必要なパッケージが多数用意されており、スクリプト言語に匹敵するぐらい使いやすいと感じた。

csv、xml、json などのパッケージもあり、excel の read/write するパッケージもあるようなので、それらと組み合わせると必要なツールが作れることだろう。

また、WEB API 化するために echo フレームワークを使ったが、とても簡単なので、興味ある方はリンク先を見ていただきたい。