commander というコマンド実行履歴分析ツールを作りました

Javaアプリケーション開発の勉強を兼ねて、「`commander`」という、ターミナルなどで打ったコマンドの履歴を分析するツールを作ってみましたので、ご紹介です。

# 概要

`commander` は、LinuxのターミナルやWindowsのコマンドプロンプトでのコマンド実行履歴を集計・分析し、人それぞれのコマンドの使い方や技を発見し、ノウハウを共有することを目的としたツールです。Javaで書かれています。

以前、[【Linux初級者向け】僕がLinuxをスムーズに操作できるようになるために心がけてきたことを晒してみる](http://qiita.com/chooyan_eng/items/b154d57a8da8911db612)という記事の中で「先輩がどんなコマンドを打っているのか観察してみる」というようなことを書いたのですが、リアルタイムで後ろから観察するのにも限界があるような気がしたので、あとからコマンドの実行履歴を振り返って共有できるツールがあればいいな、でも`history`とかそのまま見るのもちょっと面倒そうだな、という思いから作ってみました。

実行イメージはこんな感じです。

コマンド履歴を `command.txt` という名前のファイルで準備

```command.txt
   53  scalac src/main/scala/jp/co/chooyan/Person.scala 
   54  ll
   55  rm -f Person.class 
   56  gradle build
   57  vi build.gradle 
   58  gradle build
   59  vi build.gradle 
   60  gradle build
   61  vi build.gradle 
   62  which ls
   63  file /bin/ls
   64  od /bin/ls
   65  od /bin/ls | wc -l
   66  od -c /bin/ls | less
   67  od -c -x /bin/ls | less

... 省略

  542  cd ../commander/
  543  git status
  544  git add -A
  545  git commit -am "[add] class instanciator"
  546  git push
  547  ./gradlew test
  548  git status
  549  git pull
  550  git status
  551  history
  552  history > src/main/resources/command.txt
```

commander実行

```
$ java -jar commander.jar ${PWD}

{"ll":118,"javac":21,"ln":2,"ps":2,"ls":34,"file":1,"git":115,"java":16,"od":4,"cat":4,"vagrant":11,"mysql":1,"man":7,"999999999":1,"mkdir":8,"gibo":5,"which":1,"cd":54,"grep":3,"clear":1,"tree":3,"touch":1,"history":4,"l":2,"cp":3,"lll":3,"script":2,"gradle":9,"llll":1,"vi":42,"/usr/libexec/java":1,"service":1,"rm":6,"./gradlew":6,"pwd":5,"open":2}
```

詳しくは後述しますが、入力ファイル（上記の例では `history` コマンドの出力をファイル保存したもの）の読み込みロジックと分析ロジック、出力ロジックは「プラグイン」という形で自由に実装して追加することができ、またどのロジックを使うかも `config.yml` ファイルで柔軟に変更することが可能です。

以下、そのあたりの仕組みについて少し詳しく説明していきます。

なお、ソースコードは以下のgithubリポジトリに公開しています。
https://github.com/chooyan-eng/commander

# プラグイン

`commander` は、以下の特徴があります。

* 入力ファイルの読み取り、分析、出力の３つのモジュールを「プラグイン」として誰でもそれぞれ追加していくことができる。
* 各モジュールでどのプラグインを使って分析するかは`config.yml`ファイルで指定する。
* プラグインは（入出力の形式に互換性があれば）自由に組み合わせることができる。

~~僕に分析ロジックを実装する技術力がない...ゲフンゲフン~~ 分析の方法は用途や環境によってによって様々だと思いましたので、`commander` ではあえて分析ロジックを一つに決めて実装するのではなく、様々な開発者が様々な手法で分析できるように各モジュールのインターフェースだけを決める、という考え方で作られています。

例えば入力となるコマンド実行履歴も`history`だけではなく`script`という方法がありますし、分析も単純なコマンド実行回数の集計だけでなく、前後の流れも考慮した分析、パイプやリダイレクトの使い方などが分かる分析など様々な分析方法が考えられるかと思います。出力も標準出力以外にもファイル保存、DB保存、サーバーへPOSTなど様々でしょう。

そう考えると、全ての組み合わせを考えた上で実装するくらいなら、「新しいロジックがほしくなった人がロジックを自分で実装できるようにして、同じようなロジックがすでに実装されていれば簡単に使いまわせるようにした方が楽なのでは」と思ってこのような作りにした次第です。

では、プラグインとして実装可能な各モジュールについて説明します。

## パーサー（Parser）
`jp.co.chooyan.commander.core.parse`パッケージにインターフェースがあり、`jp.co.chooyan.commander.plugin.parse`パッケージに実装クラスを作ります。

`Parser`は、入力として与えられるコマンド実行履歴ファイルを解釈し、アナライザーが扱いやすいオブジェクト形式に整形するモジュールです。入力ファイルの形式の違いをここで吸収する役割を担っています。

サンプルの実装として、`SimpleCommandParser`が実装されています。`SimpleCommandParser`クラスは`history`コマンドで出力された内容を、１行ずつ履歴番号を消して`List<String>`にaddしていくだけの単純なパーサーです。


## アナライザー（Analyzer）
`jp.co.chooyan.commander.core.analyze`パッケージにインターフェースがあり、`jp.co.chooyan.commander.plugin.analyze`パッケージに実装クラスを作ります。

`Analyzer`は、`Parser`がパースしてオブジェクトに変換したコマンド履歴情報を、実装されたロジックに従って集計、分析するモジュールです。コマンド実行履歴「分析」ツールである`commander`の一番重要な部分になります。

が、現状はまだ大したことはできておらず、サンプルとして`HistoryCountAnalyzer`が実装されています。`HistoryCountAnalyzer`クラスは、`List<String>`形式のコマンド履歴から、コマンド部分だけ（オプションを切り捨てる）抜き出して、コマンドごとに集計して`Map<String, Integer>`の形式にまとめる単純なアナライザーです。

## アウトプッター（Outputter）
`jp.co.chooyan.commander.core.output`パッケージにインターフェースがあり、`jp.co.chooyan.commander.plugin.output`パッケージに実装クラスを作ります。

`Outputter`は`Analyzer`が集計・分析した結果出力したオブジェクトを、実装された出力形式や出力先へ出力するモジュールです。

サンプルの実装として、`JsonOutputter`が実装されています。`JsonOutputter`は`Analyzer`から受け取ったオブジェクトを、そのまま`Gson`を用いて標準出力へJSON形式で出力するだけのアウトプッターです。

標準出力へ出力しておけば、あとは`bash`のリダイレクトなりでファイル保存したり他のコマンドへ渡したりできて便利だよね、という発想です。決して楽をしたわけではありません。

# 設定ファイル

次に設定ファイルについてです。

設定ファイルは人間が書きやすく、かつ今後の機能拡張やプラグインの作り方によって柔軟に記述できる（と予想される）YAMLを採用しました。

ここに、どのファイルを入力として読み込んでどのプラグインを使うのか、という設定を記載します。ファイル名は`config.yml`です。

```yaml:config.yml
inputFile: command.txt
parser: jp.co.chooyan.commander.plugin.parse.SimpleCommandParser
analyzer: jp.co.chooyan.commander.plugin.analyze.HistoryCountAnalyzer
outputter: jp.co.chooyan.commander.plugin.output.JsonOutputter
```

ここに記載するプラグインの各クラスを変えることで、同じ入力ファイルでも分析方法や出力方法を変えることができる仕組みです。（2016/10/28現在ではそれぞれ１クラスずつしかありませんが。。。）

# 実行方法

実行方法は[`README.md`](https://github.com/chooyan-eng/commander/blob/master/README.md)に記載している通りです。

注意点は、実行時の引数に`config.yml`を置いたパスを絶対パスで指定する、というところでしょうか。ここで渡したパスが実行時のベースディレクトリとなり、直下に置かれた`config.yml`を読み取ったり、ベースディレクトリからの相対パスで`config.yml`に記載された入力ファイルを読み込んだりします。

詳しくは[`Executor.java`](https://github.com/chooyan-eng/commander/blob/master/src/main/java/jp/co/chooyan/commander/Executor.java)を読んでいただければ良いかと思いますが、`commander`の処理の流れはとてもシンプルです。

1. `config.yml`ファイルを読み取り、入力ファイルと各モジュールとして使用するプラグインのクラス名を取得する
2. プラグインクラスをインスタンス化する。
3. プラグイン同士の互換性をチェックする（パーサーが出力したデータ型はアナライザーが受け取れるデータ型であるか、等）
4. 入力ファイルを、パーサー -> アナライザー -> アウトプッター の順番で処理する。

これだけです。

キモとなる各プラグインのロジックは ~~他人任せ~~ このツールをもっと活用したい方に自由に実装していただければとても嬉しいです。GitHubに自分のコードを上げるのは始めてなので全く勝手が分かっていませんが、ぜひプルリクなどいただければ喜んで見ます。

# さいごに

`commander`の開発は目的の半分がJavaの勉強ということもあり、「普段仕事ではなかなかできないこと」を意識して開発してみました。例えばIDEにNetBeansを使い、テストコードを先に書くスタイルで開発を進め、gradleでビルドする、といった具合です。

詳細はまた別記事として書ければと思いますが、普段の慣れを全部取っ払って開発してみると、とても多くの学びがあったように思います。`commander`はとても簡単な、小さなツールではありますが、何を使ってどう作るかを、何の制約もなく０から考える経験は仕事ではなかなかできないことですので、作ってGitHubに上げてみてとても勉強になったな、という感想です。

このツールが、最初の目的であるCLI操作のノウハウの共有に活用できる（もしくはしてもらえる）ようになれる日を目指して今後も機能拡張していく予定ですので、興味のある方はぜひ`git clone`してみていただけると嬉しいです。