z.shをGoで書き換え、Windows対応とパフォーマンスの向上を図ろうとしている話。
なお、たたき台を完成させて華麗に公開しようとしていたところ今日に限って体調を崩してしまったので、未完成です。すみません。
ですので、書いている最中に思ったことを書き連ねます。
最終的に完成しましたら、この記事にも変更コミットをしたいと思います。
なお、進捗は後日こちらでごらんください。
https://github.com/zchee/zgo
ローカルの物は未コミットです。申し訳ない。
絶対!!
絶対本家超えてやるぞ!!!
z.sh
Goアドベントカレンダーをご覧の皆さんであれば、普段の生息地はターミナルの中だと思います。
そのターミナル内で縦横無尽にディレクトリを行き来するためのヘルパースクリプト、それがz.shです。
Bashやzshに対応し、標準のhistoryなどとは別に、今まで行き来したディレクトリパスのみを記録。
その履歴を独自のアルゴリズムでランク付けすることで、直近に訪れたディレクトリパスを上手い具合に表示してくれます。
あとはpecoなどでよしなにやってあげれば良い感じ。
多く知られているスクリプトなので、既に導入済みの方もいるのでは。
z.sh written Go
海外でも広く使われているようで、Unix入門のようなブログでも取り上げられていることを見かけます。
ただ実装はシェルスクリプトですので、バイナリにするとより速く、クロスコンパイルによってポータビリティは損なわず、より人間の感覚に近いアルゴリズムを実装できるかもしれないと思い、練習も兼ねてGoで書き直そうと思い立ちました。
また、確認していないのですがWindowsではこれ使えないのでは?ということも聞き、それではGoしかないと。
が、ぎっはぶ検索してたところ既にGoで実装されたものが見つかりました。
https://github.com/baskerville/Z
先越されてるー。むーん。と思いつつコードを見ていたのですが、本家と少し実装が違うのと、goroutineとか使ってもう少し改良できそうだったので手を加えようかなと思った次第です。
アルゴリズム
z.shの使いかたは他の有益な記事にお任せしますが、まずはz.shのソートアルゴリズムから。
シンプルなアルゴリズム実装ながら、シェルスクリプトの中ではかなり支持されている方だと思いますので、人間が直近だと感じるものに近い実装なのではないでしょうか。
frecencyというそうで、実装はまちまちながら、ブラウザの履歴表示などにもつかわれているようです。
rupa/zの実装はwikiにて。
https://github.com/rupa/z/wiki/frecency
mozillaによる簡単なfrecencyアルゴリズムの説明も見つけました。
https://dev.mozilla.jp/localmdc/localmdc_10358.html
今回のz.shの実装は、wikiから説明を抜粋。
if a directory was accessed less than an hour ago, rank is multiplied by 4
if a directory was accessed less than a day ago, rank is multiplied by 2
if a directory was accessed less than a week ago, rank is divided by 2
if a directory was accessed more than a week ago, rank is divided by 4
とありまして、簡単に翻訳しますと
- もしそのディレクトリの最終アクセスが一時間以内ならば、rankを4でかける(乗法)
- もしそのディレクトリの最終アクセスが一日以内ならば、rankを2でかける(乗法)
- もしそのディレクトリの最終アクセスが一週間以内ならば、rankを2で
割る(除法) - もしそのディレクトリの最終アクセスが一週間以上前ならば、rankを4で割る(除法)
となっています。
rankとはz.shのデータファイル($_Z_DATA)にディレクトリパスと一緒に保存されている数値です。
非常に単純でこれでいいのか感を感じさせますが、この部分は最初のコミットから変更されていないので現状これが自然に感じるベストなのでしょう。
_Z_DATA
z.shのデータファイルには、
- パス
- rank
- Unix Time
の順にデータが保存されています。
例としては
/Users/zchee/src/github.com/martine/ninja|4.36328|1449223688
といった感じ。
カレントパスに変更があった場合に、データファイル内にそのパスがあるかどうかパースして、存在したらUnix Timeを元にfrecencyアルゴリズムを使ってrankを書き換える流れです。
なお$HOMEは追加されないようになっています。
ただ.gitだったりは入ってしまったりしてるので、ここはGo実装と合わせて直したいところ。
baskerville/Z
さて
https://github.com/baskerville/Z
ですが、何点か直したいところが見つかりました。
本家とアルゴリズムが違う
先ほどの本家z.shのfrecencyアルゴリズムは実装されていないように思います。
とりあえず動作部分は本家と同一を目指したい。
その後ignoreなど設定しようかと思います。
os.Getenv("HOME")
問題
特別な環境変数に指定がない場合、ホームディレクトリパスを取得しそこに.zを置くようになってます。
しかし、確かこれはWindowsなどではまずい実装なはず。
そのためこのようなパッケージがあります。
https://github.com/mitchellh/go-homedir
注意してgo get -v
を見ていると割と使われているパッケージで、プラットフォームの違いを吸収してホームディレクトリを取得してくれます。
なお、pure GoでWin APIを使った素晴らしくヤバい無駄技術のようなものや、OS Xのframeworkをimportしてcgoで動かすなどトリッキーなことをしていない条件付きですが、
日本国内においてこういったWindowsでは動かないような実装をGoで書いた場合、すぐさま某氏からWindows対応プルリクエストが飛んでくる場合があるらしいので注意しましょう。
せっかくGoで書くので、可能な限りマルチプラットフォーム対応していきたいところです。
がつんとmain()に書かれている
多少の細分化はされていますが、実装のほとんどはmain()に書かれており少しコードの見通しが悪いかなと感じたので、もう少し分けて書き直したいと思っています。
計算とかデータ記述は並行処理できそう
rank計算やデータファイルへの記述はgoroutineで分けて、もう少し速くできそうです。
goroutine使えばいいってものでもないので、実際速くなるかどうかはやってみないとですが、希望があるならやるしかない!!
Goで書き換えるということ
いつものように少し脱線しますが…
自分がいまGoを勉強していて、Go言語の基本的な書き方のExampleは世の中に多数公開されていて非常に参考になるのですが、いざなにかそれっぽいものを書こうと思うと題材に困りました。
はじめてRailsを勉強する場合だとまず、
Ruby on Rails Tutorial - https://www.railstutorial.org/
Ruby on Rails チュートリアル- http://railstutorial.jp/
から始めるのが鉄板で、この題材はとりあえずそれっぽいSNSが出来上がるようになっています。
しかしGoの場合ですと、まずは本家A Tour of GoやEffective Goに目を通そう、というのが通例なよう。
間違いなく有益で、厚みのあるものですので熟読することが正解なのは重々承知なのですが、いかんせんコード片だけだと嬉しさがないなぁとも思ったり。
Railsをちょっとかじった時には、初心者にも書く楽しさを伝えてる良いチュートリアルだなぁと感じでいました。
C言語あがりや他の言語に疲れた人たちがGoに行き着く、Go界隈はおっさん多いらしいぞと話題にもなりましたが、これだけGoが流行るともうそんなこともなさそう。
少なくともぼくはGoが初めてちゃんと勉強してる言語です。
知らないだけで鉄板なチュートリアルがあるような気もしているので、ご存知でしたら教えてください。
ということで題材探しをしつつ、Docker Machineとかはまだ実装薄いのでdriver書いたりして勉強させてもらってますが、
ふと、pure Bashならとりあえずコード読めるし、Bashでできることは大体Goでもできる、じゃあBashで書かれたものをGoで書き直せば結構勉強になるんでは!?と気付きを得て今回片手間でやり始めました。
他にもrbenvないし他env系、sstephenson/batsとか、zsh plugin managerとかも面白そうですね。
楽しそうなのはいくらでもあるので、Go勉強中の方はBashで書かれたものの再実装、試してみるのもお勧めします。
練習なので車輪の再発明で結構ですし、動くものが作れる嬉しさとは別に、動作スピードで本家超えれたら結構嬉しいものです。
WindowsやLinux、darwinの違いも調べるきっかけにもなりそう。
まとめ
進捗0のお恥ずかしい記事になってしまいましたが、体調回復したらちょこちょこ進めていきたいです。
他のもやっているので、それとは別に日課のようなスタンスでできたらなぁと思っています。
日課、重要です。
冒頭にも書きましたが、進捗あったら変更ログ残しますので興味持たれた方はストック🙏
では。