標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

nyagos と VMware と DropBox

有償版 DropBox にはスマートシンクという、ディスクを節約する機能があって、「エクスプローラーからファイルは見えるけど、ローカルディスク上に実態は存在しなくて、ファイルを読むアクションをした時に自動ダウンロードする」という機能がある。

この機能の目的は、主にローカルディスクの容量の節約だ。DropBox をファイルサーバー代わりに使うには必須といえる。

で、物理マシンから DropBox フォルダーをls する分には何も問題はないんだけど、自分は DropBox フォルダーを VMware の共有フォルダーにして、\\vmware-host\Shared Folders\DropBox 経由で、仮想マシンからアクセスできるようにしている。

そうするとだ… 仮想マシンの中から DropBox フォルダーでls するだけで、そのフォルダーの全ファイルを自動ダウンロードしようとしてしまうのだ!なんてこったい!

確認したところ、echo * ではダウンロードされないようだ。おそらくだが、ls で表示する際、リパースポイントになっている時、それが本当のシンボリックリンクかどうかを検証するために実行している os.ReadLink が問題なのだろう!どうしたもんかなぁー
(方針、いろいろ迷ってる)

NYAGOS 4.4.0_0 を公開してました

4.4_beta からの改変点は

  • βを外した
  • バッチファイルを呼ぶ時に、/V:ON を CMD.EXE に使わないようにした
    • /V:ON だとCMD.EXE の環境変数の遅延展開がオンとなって、デフォルト状態が変わってしまうという問題があった。実質問題はないのだが、デフォルトが変わるとユーザの想定が崩れるケースだってあるはずだ

の2点だけで事実上そのままですわ。4.2→4.3 の lua53.dll → GopherLua に比べると、自分の試用期間も長かったので、ほとんど問題なかったという感じです。ライブラリもアウトソースして、実績ある人様のものに差し替えたわけですからねー

とはいえ、Linuxでは無効にしてある機能も多いので、本当に必要かどうかを見極めながら、適宜 Windows 版だけの機能の移植ですかねー

slack へポストするコードを書いた

秘密鍵書けないので、呼び出し元の main 関数は省略するが、下記のようなコードで出来た。

package main

import (
    "bufio"
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

type Message struct {
    Text    string `json:"text"`
    Channel string `json:"channel",omitempty`
}

func (json1 *Message) PostTo(webHookUrl string) error {
    bin, err := json.Marshal(json1)
    if err != nil {
        return err
    }
    fmt.Println(string(bin))

    res, err := http.Post(webHookUrl, "application/json", bytes.NewReader(bin))
    if err != nil {
        return err
    }
    fmt.Println(res.Status)
    sc := bufio.NewScanner(res.Body)
    for sc.Scan() {
        fmt.Println(sc.Text())
    }
    if err := sc.Err(); err != nil {
        fmt.Fprintln(os.Stderr, err)
    }
    defer res.Body.Close()
    return nil
}

今のところ、自分へのダイレクトメッセージしかポストできないんだよなー。たぶん、権限的な問題だろうね

今日の学び:CMD.EXE での遅延評価

nyagos では、バッチファイルを呼び出す時、環境変数の変更を取り込むため

%COMSPEC% /V:ON /S /C "call "バッチファイル名" &
set "ERRORLEVEL_=!ERRORLEVEL!" &
(cd & set > 一時ファイル名)  "

ということをしていた。

が、これ、ERRORLEVEL の値を参照するために遅延評価オプションを有効にしなくてはいけないため、バッチ実行時の CMD.EXE のデフォルト状態が変わってしまうという問題があった。

が、今日、この解決法が分かった。

リンク先の nullpo-head/source-win-bat のコードを見ると、

" & call set SW_EXITSTATUS=%^ERRORLEVEL% "

とかやってる。call set !? なんじゃそりゃ

ぐぐってみると、解説記事があった。

なるほどね!さっそく nyagos にも組み込みました!

NYAGOS 4.4.0_beta を公開してました。

Release 4.4.0_beta · zetamatta/nyagos

  • Linux サポート(実験レベル)
  • ドライブ毎のカレントディレクトリが子プロセスに継承されなかった問題を修正
  • ライブラリ "zetamatta/go-getch" のかわりに "mattn/go-tty" を使うようにした
  • msvcrt.dll を(直接syscall経由で)使わないようにした。
  • Linux でも NUL を /dev/null 相当へ
  • Lua変数 nyagos.goos を追加
  • (#341) Windows10で全角文字の前に文字を挿入すると、不要な空白が入る不具合を修正
    • それに伴い、Windows10 では virtual terminal processing(ネイティブのエスケープシーケンス機能)を常に有効に
    • git.exe push がネイティブのエスケープシーケンスを無効にしても再び有効にするようにした。
  • (#339) ワイルドカード .??* が .. にマッチする問題を修正

今回より(今回だけ?)Linux版もバイナリ公開です。

develop ブランチの運用期間も結構長いので、今回は 4.3_beta の時みたいに不具合の嵐ではないと思います、多分(ほんとかよ)

syscall は deprecated だったのかー

syscall - The Go Programming Language

Deprecated: this package is locked down. Callers should use the corresponding package in the golang.org/x/sys repository instead.

(雑な訳)非推奨:このパッケージ(syscall)は凍結(?)しとります。ユーザはかわりに golang.org/x/sys のレポジトリのうち、対応するパッケージを使うべき

マジすか!では、golang.org/x/sys/windows を見てみよう。

windows - GoDoc

The primary use of this package is inside other packages that provide a more portable interface to the system, such as "os", "time" and "net". Use those packages rather than this one if you can.

(雑な訳)このパッケージが主に使われているのは、"os""time""net"といった、システムに対してより移植性の高いインターフェイスを提供する他のパッケージの内部だ。もし可能なら、これを使うよりはそういった(汎用の)パッケージを使うべきだ。

Ok!完全に理解した(まったくわかっていない)


というわけで golang.org/x/sys/windows のマニュアルを眺めているが、自前で NewProc しなくても、あらかじめいろんな WindowsAPI が関数化されているのでいいな、これ。

たとえば、windows.GetConsoleScreenBufferInfoはコンソールバッファの情報を得る API だが、構造体(windows.ConsoleScreenBufferInfo)やハンドル(windows.Stdout/windows.Stderr)まで全部定義済みなり。

一方で、文字コード変換には MultiByteToWideChar (ANSIUnicode変換)はあるが、逆がない。あくまで golang のライブラリで使われているもの限定なので、網羅性はカオスのようだ。

また、文字列パラメータも、string ではなく、*uint16 になっているので、そのまま素で使うのはしんどい。*uint16string に変換するラッパーが作らんとね。

以上(別にオチはない)

io.Reader のプリプロセッサな io.Reader を作る

io.Reader のプリプロセッサな io.Reader を作る - Qiita より転載)

任意の io.Reader を受け取って、それを加工した結果を、別の io.Reader として読み取れるようにしたい時、どうするのがベストな方法だろうか。

具体的には、当初「文字コード(ShiftJIS or UTF8)を UTF8 に変換するフィルターを作る」といったことをしたかったのだが、文字コード変換まで含めるとコードの本当に説明したい箇所がぼやけるので、本文書では簡単に「行番号を付加する」といった例を用いて「io.Reader to io.Reader なフィルターの出来るだけお手軽な作り方」を検討した結果を報告したいと思う。

io.Pipe で作る

加工処理を別の goroutine で行い、io.Pipe の Writer の方に加工結果を出力する。ユーザは io.Pipe の Reader 側を使えばよい。

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func lnum(src io.Reader) io.Reader {
    in, out := io.Pipe()
    lnum := 0
    go func() {
        br := bufio.NewReader(src)
        for {
            line, err := br.ReadBytes('\n')
            if err != nil {
                out.CloseWithError(err)
                return
            }
            lnum++
            fmt.Fprintf(out, "%d: %s", lnum, line)
        }
    }()
    return in
}

func main() {
    sc := bufio.NewScanner(lnum(os.Stdin))
    for sc.Scan() {
        fmt.Println(sc.Text())
    }
    if err := sc.Err(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

go run lnum1.go < ファイル名」のようにして使う。この方法はお手軽で、標準ライブラリだけで完結する。ただ、goroutine を別途立ち上げるので(大したことはないだろうが)余分なリソースを使う点、また途中で読むのを止めた時に goroutine が残ってしまわないよう対策が必要な点などが課題だ。

"tidwall/transform" を使う

こういうフィルター処理に最適なライブラリを探していたところ、よいものが見つかった。

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"

    "github.com/tidwall/transform"
)

func lnum(src io.Reader) io.Reader {
    br := bufio.NewReader(src)
    lnum := 0
    return transform.NewTransformer(func() ([]byte, error) {
        line, err := br.ReadString('\n')
        if err != nil {
            return nil, err
        }
        lnum++
        return []byte(fmt.Sprintf("%d: %s", lnum, line)), nil
    })
}

func main() {
    sc := bufio.NewScanner(lnum(os.Stdin))
    for sc.Scan() {
        fmt.Println(sc.Text())
    }
    if err := sc.Err(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

特に問題ないが、強いて言うならば標準ライブラリで完結しないくらい?(それは問題ちゃうやろう)

"golang.org/x/text/transform"

こちらは準・標準的なライブラリで、

type Transformer interface {
    Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
    Reset()
}

というインターフェイスで型を実装すれば、

transform.NewReader(元Reader, 実装したTransformerのインスタンス)

という形式で、変換を実装する io.Reader を作成してくれる。

が、これ Transform メソッドを実装するのが非常に面倒くさい。src や dst のサイズが任意ではなく、呼び出し元から決められているせいだ。一回、実装してみたが、dst があふれそうな時、別のバッファに残しておいて次回に処理するとか、なかなか大変だった(やり方が悪いのかもしれない)。

ただ、メモリアローケーション回数が少ないはずなので、速度的には有利で、うまく実装できれば非常に実行効率がよいフィルターが作れると思われる。

なんかよい実装例ないものか(誰か、わかりやすい日本語の解説記事書いてよ)

以上

追記

"tidwall/transform" と似たようなものを作ろうとしたのですが、ベンチを取ると、やはり"tidwall/transform" にはかないませんでした。

goos: windows
goarch: amd64
pkg: github.com/zetamatta/go-texts/preprocessor
Benchmark_filter-4             10000        109332 ns/op       18220 B/op        321 allocs/op
Benchmark_transformer-4        10000        108897 ns/op       15627 B/op        319 allocs/op
Benchmark_iopipe-4             10000        230557 ns/op       15874 B/op        323 allocs/op
PASS
ok      github.com/zetamatta/go-texts/preprocessor  4.733s

ソースは https://github.com/zetamatta/go-texts/blob/master/preprocessor/preprocess_test.go を御覧ください

  • Benchmark_filter-4 … 自作品
  • Benchmark_transformer-4"tidwall/transform"
  • Benchmark_iopipe-4 … io.Pipe と goroutine によるもの

io.Pipe と goroutine では仕組みが大掛かりなせいか、2倍近くコストがかかっています。自作のものは bytes.Buffer を2つ交互に使ってバッファリングしているのですが、あと一歩及びません。

"tidwall/transform"のソースを見てみると、[]bytes を使って溢れたデータを管理しているのですが、領域を開放せずに長さだけゼロにする (bytes.Buffer)Reset 的な操作を

r.buf = r.buf[:0] // reset the read buffer, keeping it's capacity

とやっていました。同操作を[]byte でやるのにどうするんだと思っていたのですが、なるほどなぁ…です。