2017年5月5日金曜日

Go言語でスクリプトを書いてみる

Go言語をスクリプトにも利用するための練習として、以前書いたスクリプトをGo言語で書いてみました。エラー処理をかっとばして書けばGoもかなりコンパクトに書けます。Rubyでは49行でしたが、Goでも70行くらいで書けました。色々面倒なところはありますが、何とかなる範囲ではあるかも。

package main

import (
  "fmt"
  "os"
  "os/exec"
  "path/filepath"
  "time"
  "github.com/mattn/go-shellwords"
  "github.com/mattn/go-zglob"
)

func html_minifier(from string, to string) {
  cmd := fmt.Sprintf(`
  sh -c 'html-minifier %v \
    --collapse-boolean-attributes \
    --collapse-whitespace \
    --remove-attribute-quotes \
    --remove-comments \
    --remove-empty-attributes \
    --remove-redundant-attributes \
    --remove-script-type-attributes \
    --remove-style-link-type-attributes \
    --html5 \
    --use-short-doctype \
    --minify-js \
    --minify-css > %v'
  `, from, to)
  args, _ := shellwords.Parse(cmd)
  exec.Command(args[0], args[1:]...).Run()
}

func main() {
  dir := os.Args[1]
  var minifySize, html_minifierSize, originalSize int64
  var minifyTime, html_minifierTime int64

  paths := getPaths(dir, "*.html")

  for _, path := range paths {
    fileinfo, _ := os.Stat(path)
    originalSize += fileinfo.Size()
  }

  for _, path := range paths {
    cmd := fmt.Sprintf("sh -c 'minify %s > %s'", path, path+".min")
    args, _ := shellwords.Parse(cmd)
    t := time.Now().UnixNano()
    exec.Command(args[0], args[1:]...).Run()
    minifyTime += time.Now().UnixNano() - t

    fileinfo, _ := os.Stat(path+".min")
    minifySize += fileinfo.Size()
  }

  for _, path := range paths {
    t := time.Now().UnixNano()
    html_minifier(path, path+".min")
    html_minifierTime += time.Now().UnixNano() - t

    fileinfo, _ := os.Stat(path+".min")
    html_minifierSize += fileinfo.Size()
  }

  fmt.Printf("file num: %v\n", len(paths))
  fmt.Printf("original:      %v\n", originalSize)
  fmt.Printf("Minify:        %v, %v\n", minifySize, time.Duration(minifyTime))
  fmt.Printf("html_minifier: %v, %v\n", html_minifierSize, time.Duration(html_minifierTime))
}
Goで即興スクリプトを書いてみて気になった点は3つほど。(1)RubyのDir.globみたいな再帰検索はこれまで自作していましたが、最近はgithub.com/mattn/go-zglobを利用すると簡単に書けるようになりました。どんどんmattnさんのライブラリで埋まっていく…。

(2)ベンチマークにはtestingという標準ライブラリがあります。時間やメモリを計測するには非常に便利ですが、計測精度を高めるために勝手に連続実行する仕組みになっています。同時に色々な情報を処理しながら計測しようとすると少し困ります。手軽な計測であればtime.Now()で地道に計算するのが楽かなあ。

(3)外部コマンドを実行するにはexec.Command(args)を使いますが、argsには文字列の配列を渡す必要があり面倒です。github.com/mattn/go-shellwordsを利用するとシェル上での実行文字列を配列に変換してくれるのでかなり楽になります。ただしcommand > outのようなコマンド内での出力には対応していないので、sh -c 'command > out'のような形式に変更して実行する必要がありました。

Go言語はスクリプトごとにbuildしていると容量がかなり無駄になりますが、以下の方法でバイナリサイズを小さくすれば気にならない程度には小さくなります。
$ sudo apt-get install upx-ucl
$ go get github.com/pwaller/goupx
$ go build -ldflags '-s' test.go
$ goupx -9 test  # or goupx test

もう少し色々なパターンで記述の容易さを確認してみたいです。

0 件のコメント: