例えば、正規表現を避ける
TIME rest time current/total
TopicsPlaceHolder

例えば、正規表現を避ける

Gopher night #1

Mar 17th, 2016

Profile

songmu

 

最近作ったgoプロダクト

prompter

Goでプロンプト簡単に出すやつ

twitterID := prompter.Prompt("Enter your twitter ID", "")
lang := prompter.Choose("Which language do you like the most?", []string{"Perl", "Golang", "Scala", "Ruby"}, "Perl")
passwd := prompter.Password("Enter your password")
var likeSushi bool = prompter.YN("Do you like sushi?", true)
var likeBeer bool = prompter.YesNo("Do you like beer?", false)

以下の様な要件を満たす。

goshim

$ go get github.com/Songmu/goshim/cmd/goshim

より良い go run を実現 → スクリプト的にgoを実行したい場合に便利

$ goshim ./script/ore/pkgdir [args...]

例えば、正規表現を避ける

Goの正規表現

大前提

regexp.MustCompile はトップレベルもしくはinit()内でのみ使う

var reg = regexp.MustCompile(`...`)

やむを得ず動的に組み立てたい場合

regexp.Compile を使う

reg, err := regexp.Compile(`...`)

文字列はインデックスアクセスができる

str[:4]
str[1:3]
str[3:]

stringsのベンリ関数達

ある文字列を含む

ok := strings.HasPrefix("abcde", "abc")

空白文字で区切る

fields := strings.Fields("a b c  d e")

任意の文字で区切る

strings.Split("a,b,c", ",")

前後の余計な文字を削る

string.TrimSpace("   a b c   ")

文字列置換

str := strings.Replace("Perl Perl", "Perl", "Golang", -1)

まとめ

第2部

horenso

horenso 報・連・相

cronなどのバッチジョブにおける課題

解法: ラッパーコマンドを作成する

% /path/to/wrapper /path/to/job ...

ラッパーコマンドが、実行コマンドを受け取り、そのコマンドを実行した後に、事後処理をおこなう

例1 cronlog

@kazuho さんによるPerlスクリプト

% cronlog -- ping -n 5 my-server 2>&1

ジョブが失敗した時だけ、出力を出す
→ cronに指定すると、失敗した時だけメールが飛ぶとかできる

cronlogの特性

例2 App::RunCron

拙作のPerl製ジョブ通知フレームワーク

例3 独自にラッパースクリプトを書く

% ./wrapper.(sh|pl) /path/to/job ...

そこでhorenso

インストール

https://github.com/Songmu/horenso/releases よりバイナリ取得可能

go get する手もあります

% go get github.com/Songmu/horenso/cmd/horenso

horensoの適用

$ /path/to/job args...

こういうジョブに対して

horensoの適用

$ horenso --reporter reporter.pl -- /path/to/job args...

このようにラップする。(reporter.plは任意の通知用スクリプト・後述)

reporter

reporterに渡されるJSONのサンプル

{
  "command": "perl -E 'say 1;warn \"$$\\n\";sleep 2'",
  "commandArgs": [
    "perl",
    "-E",
    "say 1;warn \"$$\\n\";sleep 2"
  ],
  "output": "57078\n1\n",
  "stdout": "1\n",
  "stderr": "57078\n",
  "exitCode": 0,
  "result": "command exited with code: 0",
  "hostname": "MatsukiMasayuki-no-MacBook-Pro.local",
  "pid": 57078,
  "startAt": "2016-02-16T01:27:08.946009881+09:00",
  "endAt": "2016-02-16T01:27:11.010627341+09:00",
  "systemTime": 0.03176,
  "userTime": 0.025954
}

例) reporter.pl

use JSON::PP;
my $report = decode_json <>; // 結果を取り出す
$report->{startAt};
...

例) reporter.go

import (
    "encoding/json"
    "os"
    "github.com/Songmu/horenso"
)
func main() {
    var d = horenso.Report{}
    json.NewDecoder(os.Stdin).Decode(&d)
    d.StartAt
    ...
}

ジョブ開始前に通知する(noticer)

% horenso --noticer 'ruby noticer.rb' \
    -r reporter.pl -- /path/to/job args...

時間のかかるジョブの実行前に通知させたい場合

reporterの複数指定

% horenso -r reporter.pl -r reporter2.py -- /path/to/job args...

複数のreporterを指定した場合、平行に実行される(Goっぽい)

horensoを使ってラッパーシェルを作る

$ cat ./wrapper.sh
#!/bin/sh
exec /path/to/horenso \
  -n /path/to/noticer.py         \
  -r /path/to/reporter.pl        \
  -r 'ruby /path/to/reporter.rb' \
  -- "$@"

これを例えば以下のようにcrontabに指定する

3 4 * * * /path/to/wrapper.sh /path/to/job --args... 2>&1 | logger -t myjob

できること一例

別にcronじゃなくても、バッチジョブ実行の仕組みに対して統一的に導入すると便利。

喜びの声

現在いただいている要望

実装上の工夫

標準出力、エラー出力を奪わずに、内容を変数に保持する

Goっぽさある。

// コマンドからパイプを取り出す
cmd := exec.Command(args[0], args[1:]...)
stdoutPipe, _ := cmd.StdoutPipe()
stderrPipe, _ := cmd.StderrPipe()

// 出力を格納する bytes.Buffer を宣言
var bufStdout bytes.Buffer
var bufStderr bytes.Buffer
var bufMerged bytes.Buffer

// pipeの書き出し先 io.TeeReader で追加
stdoutPipe2 := io.TeeReader(stdoutPipe, io.MultiWriter(&bufStdout, &bufMerged))
stderrPipe2 := io.TeeReader(stderrPipe, io.MultiWriter(&bufStderr, &bufMerged))

// コマンドの実行と io.Copy
cmd.Start()
go func() {
    defer stdoutPipe.Close()
    io.Copy(os.Stdout, stdoutPipe2)
}
go func() {
    defer stderrPipe.Close()
    io.Copy(os.Stderr, stderrPipe2)
}
cmd.Wait()

// 取り出す
out := bufStdout.String()
stderr := bufStderr.String()
merged := bufMerged.String()

コマンドからパイプを取り出す

cmd := exec.Command(args[0], args[1:]...)
stdoutPipe, _ := cmd.StdoutPipe()
stderrPipe, _ := cmd.StderrPipe()

出力を格納する bytes.Buffer を宣言

var bufStdout bytes.Buffer
var bufStderr bytes.Buffer
var bufMerged bytes.Buffer

pipeの書き出し先 io.TeeReader で追加

stdoutPipe2 := io.TeeReader(stdoutPipe, io.MultiWriter(&bufStdout, &bufMerged))
stderrPipe2 := io.TeeReader(stderrPipe, io.MultiWriter(&bufStderr, &bufMerged))

コマンドの実行と io.Copy

cmd.Start()
go func() {
    defer stdoutPipe.Close()
    io.Copy(os.Stdout, stdoutPipe2)
}
go func() {
    defer stderrPipe.Close()
    io.Copy(os.Stderr, stderrPipe2)
}
cmd.Wait()

出力を取り出す

out := bufStdout.String()
stderr := bufStderr.String()
merged := bufMerged.String()

実行プログラムの終了コードやシグナルを取得する

err := cmd.Run()
exitCode := 0
if e, ok := err.(*exec.ExitError); ok {
    if w, ok := e.Sys().(syscall.WaitStatus); ok {
        if w.Signaled() {
            // 無理やり感
            exitCode = int(w) | 0x80
        } else {
            exitCode = s.ExitStatus()
        }
    }
    exitCode = -1
}

https://github.com/Songmu/wrapcommander に切り出し

各行の出力をhookしてtimestampを付ける

以上

We are Hiring

hatena