シェル/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 フレームワークを使いましたが、とても簡単なので、興味ある方はリンク先を見ていただきたいです。

1969年生まれ。大学卒業後から15年以上にわたり、通信、カードリーダ、セキュリティ業界においてソフトウェア開発に従事。その後、2012年5月に当社を設立。電力、交通、車載向けの組み込み系システム、旅行業界向けの WEB システム開発、音声合成システム、消防向けのシステム開発等に参画。
低コストかつシンプルで安定稼働するシステムの実現を目指し、アーキテクチャ設計に取り組んでいます。
会社情報と代表者守屋のプロフィール詳細