Javaアプリケーション開発の勉強を兼ねて、「commander
」という、ターミナルなどで打ったコマンドの履歴を分析するツールを作ってみましたので、ご紹介です。
概要
commander
は、LinuxのターミナルやWindowsのコマンドプロンプトでのコマンド実行履歴を集計・分析し、人それぞれのコマンドの使い方や技を発見し、ノウハウを共有することを目的としたツールです。Javaで書かれています。
以前、【Linux初級者向け】僕がLinuxをスムーズに操作できるようになるために心がけてきたことを晒してみるという記事の中で「先輩がどんなコマンドを打っているのか観察してみる」というようなことを書いたのですが、リアルタイムで後ろから観察するのにも限界があるような気がしたので、あとからコマンドの実行履歴を振り返って共有できるツールがあればいいな、でもhistory
とかそのまま見るのもちょっと面倒そうだな、という思いから作ってみました。
実行イメージはこんな感じです。
コマンド履歴を 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
は、以下の特徴があります。
- 入力ファイルの読み取り、分析、出力の3つのモジュールを「プラグイン」として誰でもそれぞれ追加していくことができる。
- 各モジュールでどのプラグインを使って分析するかは
config.yml
ファイルで指定する。 - プラグインは(入出力の形式に互換性があれば)自由に組み合わせることができる。
僕に分析ロジックを実装する技術力がない...ゲフンゲフン 分析の方法は用途や環境によってによって様々だと思いましたので、commander
ではあえて分析ロジックを一つに決めて実装するのではなく、様々な開発者が様々な手法で分析できるように各モジュールのインターフェースだけを決める、という考え方で作られています。
例えば入力となるコマンド実行履歴もhistory
だけではなくscript
という方法がありますし、分析も単純なコマンド実行回数の集計だけでなく、前後の流れも考慮した分析、パイプやリダイレクトの使い方などが分かる分析など様々な分析方法が考えられるかと思います。出力も標準出力以外にもファイル保存、DB保存、サーバーへPOSTなど様々でしょう。
そう考えると、全ての組み合わせを考えた上で実装するくらいなら、「新しいロジックがほしくなった人がロジックを自分で実装できるようにして、同じようなロジックがすでに実装されていれば簡単に使いまわせるようにした方が楽なのでは」と思ってこのような作りにした次第です。
では、プラグインとして実装可能な各モジュールについて説明します。
パーサー(Parser)
jp.co.chooyan.commander.core.parse
パッケージにインターフェースがあり、jp.co.chooyan.commander.plugin.parse
パッケージに実装クラスを作ります。
Parser
は、入力として与えられるコマンド実行履歴ファイルを解釈し、アナライザーが扱いやすいオブジェクト形式に整形するモジュールです。入力ファイルの形式の違いをここで吸収する役割を担っています。
サンプルの実装として、SimpleCommandParser
が実装されています。SimpleCommandParser
クラスはhistory
コマンドで出力された内容を、1行ずつ履歴番号を消して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
です。
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現在ではそれぞれ1クラスずつしかありませんが。。。)
実行方法
実行方法はREADME.md
に記載している通りです。
注意点は、実行時の引数にconfig.yml
を置いたパスを絶対パスで指定する、というところでしょうか。ここで渡したパスが実行時のベースディレクトリとなり、直下に置かれたconfig.yml
を読み取ったり、ベースディレクトリからの相対パスでconfig.yml
に記載された入力ファイルを読み込んだりします。
詳しくはExecutor.java
を読んでいただければ良いかと思いますが、commander
の処理の流れはとてもシンプルです。
-
config.yml
ファイルを読み取り、入力ファイルと各モジュールとして使用するプラグインのクラス名を取得する - プラグインクラスをインスタンス化する。
- プラグイン同士の互換性をチェックする(パーサーが出力したデータ型はアナライザーが受け取れるデータ型であるか、等)
- 入力ファイルを、パーサー -> アナライザー -> アウトプッター の順番で処理する。
これだけです。
キモとなる各プラグインのロジックは 他人任せ このツールをもっと活用したい方に自由に実装していただければとても嬉しいです。GitHubに自分のコードを上げるのは始めてなので全く勝手が分かっていませんが、ぜひプルリクなどいただければ喜んで見ます。
さいごに
commander
の開発は目的の半分がJavaの勉強ということもあり、「普段仕事ではなかなかできないこと」を意識して開発してみました。例えばIDEにNetBeansを使い、テストコードを先に書くスタイルで開発を進め、gradleでビルドする、といった具合です。
詳細はまた別記事として書ければと思いますが、普段の慣れを全部取っ払って開発してみると、とても多くの学びがあったように思います。commander
はとても簡単な、小さなツールではありますが、何を使ってどう作るかを、何の制約もなく0から考える経験は仕事ではなかなかできないことですので、作ってGitHubに上げてみてとても勉強になったな、という感想です。
このツールが、最初の目的であるCLI操作のノウハウの共有に活用できる(もしくはしてもらえる)ようになれる日を目指して今後も機能拡張していく予定ですので、興味のある方はぜひgit clone
してみていただけると嬉しいです。