概要
会社で自分の強みというテーマの勉強会があったので
CLIのカスタマイズについて発表しました。
自分はシェル芸人でもないし著名なVimプラグイン開発社でも無いのですが、
今の会社に入社して七ヶ月ほどして、
意外とローカルマシン上でも素のbashでCLI操作している人が多いことに気が付きました。
CLIカスタマイズしている人と語り合いたいと常々思っていた私は、
会社内でそういう人を作ることを目指して資料を作り、軽く引かれるくらいの熱量で30分ほどプレゼンしました。
しかし30分で語れることなどたかが知れています。
語りきれなかった自分の考えをここにまとめ、プレゼンの補足資料としたいと思います。
発表資料は以下の通りです。GodをGotとスペルミスしましたが自分への戒めとして残したままにしています。
1. なんでCLIなんて使ってんの?マゾなの?
マゾなの?という問に関してはまあおそらくYESであるといえます。
土日潰して黒魔術じみたdotfilesをいじっていると、楽しいと感じるのと同じくらい苦悩しております。
進んでこんなことやる私はマゾに違いありません。
そもそも前提が違う(CLIとGUIでは)
普段CUIを使用しない皆さんからするとCLIを積極的に使用している人が、
なぜCLIを使用しているのか不思議に思うことでしょう。
以前の私もそう思っていました。
以前の私はCLIの事をとても不親切で使いづらい旧世代の遺物と思っていました。
昔は低性能なコンピュータであったため、グラフィカルな表現をするのは困難だった。
文字ベースでコンピュータを操作しようとしたがために、非常に使いづらいインタフェースにするしかなかった。
しかし今は、低スペックマシンであってもグラフィカルを表現するに十分な性能を持っている。
なのでマシンパワーを活かし、解りやすくかつ強力な操作ができるGUIを使うほうが効率が良いに決まっている。
と思っていました。
ですが、そもそもとしてその前提に大きな誤りがあるということを、後に知りました。
少し長いですが、『UNIXという考え方』という本にその違いが明確に記載されていました。
少し長いですが記載させていただきます。
オペレーティングシステムはその性質上、創造者の哲学を身にまとって現れる。
Apple者のMacOSは、高度に資格に訴えるオブジェクト思考の インタフェースを持ち、
「ほら、あなたのすぐ目の前に」というキャッチフレーズと共に登場した。
マイクロソフト社のMS-DOSは、文句なしにパーソナルコンピュータ革命のリーダーであり、
デスクトップに「メインフレームの香り」をもたらした。
DECのOpenVMSでは、ユーザは「電子思考機械」を恐れているということを前提にしていた。
ある仕事を行うためにユーザーに与えられる選択肢は、とても少なくかつ強力なものだった。
UNIXの創造社たちは、ある強力なコンセプトから始めた。ユーザは初めからコンピュータを使えるとみなしたのだ。
UNIXは「ユーザは、自分が何をしているかわかっている」との前提に立っている。
他のオペレーティングシステムの設計者が、初心者から専門家まで幅広いユーザーを受け入れようとして苦労しているとき、
UNIXの設計者たちは「何をしているのか分からないのなら、ここにいるべきではない」という不親切極まりないアプローチを選んだ。
ここではUNIXと書かれていますが、現代におけるLinuxのCLIにもこの思想は脈々と受け継がれていると考えています。
なんでCLIなんて使ってんの?
前項でCLIとGUIの思想の違いについてはわかっていただけたかと思います。
しかし、思想はわかっても具体的にCLIに利点があるのかという疑問は以前残ったままであると思います。
私もCLIちゃんと使いだしたきっかけは
「なるほどCLIを使って超効率な作業をしているプログラマーは(プログラマー)からモテるんだな」
とか
「Geekっぽくて最高にCool」
だったのでここらで腰を据えて、ちゃんと整理しようと思います。
でないと私がCLIを触っているときに、GUIを使用したプログラマーが私よりも遥かに効率的に作業をこなしている。
という不安を抱える羽目になります。由々しき事態です。
CLIは色々速い
ざっくりいえばCLIはいろいろ速いです。
まず起動が速いです。私は開発業務に必要な操作をほぼすべて(ブラウジング以外)CLIで実施可能なため、
ターミナルエミュレータを起動すれば、必要なアプリケーションを起動しているも同然です。
ターミナル上でVimを起動しても100ms前後で起動します。これは人間の体感から言えばほぼ一瞬といえます。
GUIではIDE, Gitクライアント, DBクライアントなど色々と起動する必要があると思います。
一秒を満たず同様の機能が一通り揃うCLIは速いと言えるのではないでしょうか。
次にレスポンスが速いです。CLIでは情報表示はすべて文字情報で行われます。
対しGUIではそのグラフィカルなインタフェースを描画することに多くの時間がかかります。
HTMLをレンダリングするウェブアプリケーションならばその差は更に広がることでしょう。
更に上記に述べたように、ターミナルは起動すれば各種アプリケーションを抱合している状態です。
その為、テキスト編集やGit操作、DB操作をすべてターミナル上で完結できるため、
ウィンドウの移動が不要になります。マウスを触る必要もありません。
拡張性が高い
CLIのコマンド群はそれぞれが非常にコンパクトに設計されていおり、
一つのコマンドの出力結果を他のコマンドに渡して処理するといったコマンド同士の連携が容易できるようになっています。
これはUNIXの思想としてよく挙げられている
『Small is beautiful. (小さいものは美しい)』がよく現れている部分と言えるでしょう。
稚拙ではありますが私が実際に使用している例を挙げます。
以下のコマンド群は実行日付のmarkdownファイルを~/diary配下に作成しVimで開くdiaコマンドを定義したものです。
function open_diary() {
  mkdir -p ~/diary/$(date "+%Y/%m")
  vim ~/diary/$(date "+%Y/%m/%d.md")
}
alias dia=open_diary
このように思いついた拡張を容易に実現できる点はCLIの大きな利点であるといえます。
現に私は仕事中に不満を感じた部分をToDoリストにまとめ、毎日スクリプトの修正を実施しています。
これにより非常に短いサイクルでの作業改善が実施できます。
CLIは思考を操作に直結する
CLIに慣れてくると何かを操作しようと考えてから、
コマンド実行するまでの間がGUIに比べて非常に短くなっていることに気が付きました。
これはなぜかと考えた末、CLIは思考を操作に直結するという結論に至りました。
例えばGitで管理されたリポジトリに対してファイルを一つ追加した後、ファイルをローカルリポジトリに追加したいとします。
CLIからGitコマンドを実行する場合次のような操作になります。
前提として対象リポジトリに既に移動しているものとします。
- 
git add {対象ファイル}を実行 
GitクライアントにAtlassian製のSourceTreeを使っていれば次のような操作になります。
前提として対象リポジトリを既にSourceTreeで開いているものとします。
- 左のサイドバーから
File statusを探す - マウスカーソルを
File statusに合わせる - 
File statusをクリック - 
Unstage filesのリストから対象ファイルを探す - マウスカーソルを対象ファイルの左にあるチェックボックスに合わせる
 - チェックボックスをクリック
 
さてこれを読んでどう思ったでしょうか?
こじつけでGUI側の手順を増やしていると思われたかもしれません。
しかし、事実GUI操作のためには、基本的に視認とカーソル移動を実施する必要があります。
比較してCLIでは、操作をしたいと考えてから、操作を実行するためのコマンドを頭で構築し、
そのコマンドをプロンプトに入力するだけで終わります。
よってCLIは思考を操作に直結しているといえます。
CLIを不便に感じるという方はコマンド構築するための前情報がないため、煩わしさを感じるのではないでしょうか。
ですがこれはUnixの設計思想である
「何をしているのか分からないのなら、ここにいるべきではない」
を体現しているとも言えるのではと思います。
2. 今日からできるCLI改善TIPS
CLIをカスタマイズするとき、私はいつも自分の部屋の模様替えをしているみたいだ。と感じています。
自分が快適に暮らせるように本棚やベッド、机を動かしたりしますよね?
便利な道具や家電を購入して面倒な作業を省いたりしますよね?
CLIのカスタマイズするのもそれらの振る舞いと違いはないのではないでしょうか。
CLIの利点をこれまで上げてきましたが、それでもGUIが解りやすく強力なツールであることに変わりはありません。
それでもGUIではなくCLIを使うという選択に意味を持たせるには、
いつくものコマンドやプラグインを組み合わせ、より作業を効率化する必要があると私は考えています。
そしてカスタマイズした道具を手になじませ、自分の思考どおりにCLIを操作できるようになることで、
CLIは最高だぜ。と思えるのです。少なくとも私はそう思っています。
2-1. 強力なシェルを使おう
CLIの改善を調べて最初に出てくるのが、zsh使えとかfish使えといった記事ではないでしょうか。
実際にCLIで作業をしていると、コマンドを打つのがめんどくさいと思うようになるかと思います。
GUIでは新規作成などをしなければ、表示されている項目を選択するだけなので当然でしょう。
zshはその強力な補完とヒストリ参照によってコマンド入力を補助してくれます。
補完
CLI操作で補完は非常に重要な役割を持ちます。
CLIでファイルに操作をする場合、ファイルを確認するためにlsでファイルの一覧を表示し、
ファイルを認識した上で再度コマンドを打ち込む必要があります。
しかしそれは、一回手順が増えるのでこれは非常に煩わしいことです。
補完を使えばその一回を削減することが可能です。
macやlinuxディストリビューションでデフォルトとして採用されているbashと比較し、
zshの補完は痒いところに手が届き、補完対象が幅広いのが特徴です。
例えば以下のようなファイルがあり、.gitignoreをcatで見たいとき
bashであればcat .gitまで入力して<tab>で補完しようとすると、何度<tab>を押しても候補が表示されるだけです。
zshであれば<tab>を2回押せばcat .git3回押せばcat .gitignoreまでプロンプト上に入力してくれます。
drwxr-xr-x  16 lighttiger2505  staff      544 Oct 18 22:36 .git
-rw-r--r--   1 lighttiger2505  staff      280 Sep 28 23:13 .gitignore
シチュエーションに合わせた柔軟な補完も可能です。
Gitでファイルをaddする際に、addしたいファイルがあるのがわかっていてもファイル名がわからないという時などには、
git add まで打ち込んで<tab>を押せば、git status でunstagedなファイルのみを補完してくれます。
またgitのbranchやlogなどのサブコマンドや--allや--editなどのオプションなどを補完することが可能です。
ヒストリ
CLIでは入力が煩わしくなるような長大なオプションや普段打ち込まないで忘れてしまったコマンドなどがあると思います。
入力コマンドを記録したヒストリは、これらを補助する上で非常に重要になります。
bashはctrl+rで検索モードに入ることができ、検索モード中にコマンド入力すると、
直近のヒストリからインクリメンタルサーチで入力コマンドを検索することができます。
そこから更にctrl+rで更に過去に、ctrl+sで戻ることができます。
しかしこれではコマンド入力途中に、ヒストリ検索がしたくなっても入力途中のコマンドを破棄する必要があります。
zshでは入力途中に入力内容で絞り込んだヒストリ検索が可能です。
プロンプト
プロンプトをカスタマイズすると玄人感がでて非常にgoodです。
そこにさらに付加情報をのせると、効率もあがるので一石二鳥ですね。
私のプロンプトは以下の情報を表示させています。
- コマンド成否
- コマンドの成否により色を変更します。
- 成功 : Green
 - エラー : Red
 
 
 - コマンドの成否により色を変更します。
 - ユーザ名
 - 現在のブランチ
- Gitリポジトリの現在のブランチを表示
 
 - バックグラウンドで起動しているVimの有無
- 
ctrl-zでバックグラウンドに送ったVimのジョブがあればVimと表示 
 - 
 - Line Editorのモード
- 
ctrl-[でVimモードになるようにしているため、現在のモードを表示- nomal
 - insert
 - visual出したいですが、条件判定ができなくてこまってる
 
 
 - 
 - カレントディレクトリ
 
Line Editorのモードに関しては心のShell師匠 @b4b4r07 さんの記事を参考にさせていただきました。
コマンドライン編集機能 Zsh Line Editor を使いこなす
2-1. 追加コマンド/プラグインで快適な仕事を
zshを入れて設定すれば十分に強力なCLIではありますが、
追加のコマンド/プラグインを導入することで更に強力になります。
zsh-syntax-highlighting
zshで入力したコマンドに syntax highlight が適用されます。
実在しているコマンドやファイルがひと目で分かるのでこれだけでタイポを防止できます。
zsh-users/zsh-syntax-highlighting
zsh-autosuggestions
ヒストリを更に強力にします。
コマンドを入力すると、直近のヒストリから前方一致したコマンドをサジェストします。
これは現在入力中にヒストリを参照した場合にどのようなコマンドになるかが入力中にわかるため、
ヒストリを参照すべきか否かという判断の助けとなります。
動画を参照するとわかりやすいでしょう。動作イメージ
fzf
類似ツールがいろいろあり、個人的にはこれらのジャンルをfuzzy matcherと呼んでます。
類似のプラグインにはpecoなどがあります。
fizzy matcherが提供する機能は非常にシンプルで、
stdinをリスト表示し、検索ワード入力による絞り込みをした後、リスト要素を選択するという機能だけです。
単体で見ると、それがなんだ。といわれそうですが、
UNIXの思想の一つである「ソフトウェアを梃子として使う」にこれほど適したツールはそうそうありません。
補完の話でも若干触れましたが、複数存在する操作対象に対して、何か操作をする時に実施する
以下の一連の操作は非常に煩わしいです。
- 操作対象(例えばファイル)を一覧表示する
 - 一覧を見てコマンドの対象を入力する
 
fzfはその煩わしさを解消する上で非常に強力なツールであると言えるでしょう。
例えばGitリポジトリで管理されたプロジェクトでブランチを切り替える場合、以下の手順になります。
- 
git branch -aでブランチの一覧を表示 - ブランチの一覧からチェックインしたいブランチを見つける
 - 
git checkout {ブランチ名}でブランチを切り替えする 
fzfを使用すれば以下のようになります。
- 
git checkout `git branch --all | grep -v HEAD | fzf`でgitブランチを一覧表示 - 検索ワードで絞り込み
 - 対象のブランチを選択すると同時にチェックアウトを実行
 
コマンド一つで一覧表示〜絞り込み〜選択がシームレスに実行されていることがわかると思います。
CLIのコマンドはいずれも統一的なインタフェース(stdin, stdout, stderr)を持っているため、
fzfはstdoutに一覧が表示されているようなものであればありとあらゆるものをfzfのインタフェースにのせることが可能です。
公式のwikiに大量のサンプルが記載されていることからもその汎用性の高さが伺えます。
fzf公式サンプル
おい、peco もいいけど fzf 使えよ
enhancd
CLIで面倒な事を挙げろといわれれば、きっと多くの人はディレクトリ移動がだるいというのではないでしょうか。
enhancdを使えばその悩みは解決します。(おそらく)
enhancdは前述したfzfを使用して、これまでのディレクトリ移動の履歴を一覧表示し、選択したディレクリにジャンプします。
通常のcdをオーバーライド?しているため、cdで実行できるのが非常に嬉しいところです。
また実行の仕方により幾つか動作の種類があり
- 
cd: 過去のディレクトリ移動履歴を一覧表示 - 
cd ..: 親ディレクトリ階層を一覧表示 - 
cd {ディレクトリ名}: ディレクトリ名を推測して一意であれば移動 
といったように非常に芸が細かく、それでいてデフォルトのcdを邪魔しません。
ghq
ghqを使えば大量のリポジトリを効率的に管理できます。
私の端末でも50はくだらないGitリポジトリが存在します。
もし読者がズボラであれば、適当にリポジトリを配置してしまい、
どこにいれたかわからないという経験があるのではないでしょうか。
そうなる前にしっかりと規約に沿ったリポジトリ配置をおすすめします。
私のGitリポジトリは以下のように管理されています。
この配置はGolangのディレクトリ配置を参考にしています。
├── github.com
│   ├── Shougo
│   │   ├── deoplete.nvim
│   │   └── shougo-s-github
│   ├── b4b4r07
│   │   ├── dotfiles
│   │   └── enhancd
│   ├── lighttiger2505
│   │   ├── ghs
│   │   └── lab
│   ├── mattn
│   │   ├── go-colorable
│   │   └── go-runewidth
しかしこのような構成を手動で守り続けるのは非常に煩わしいと思います。
ghqを使用すればgit cloneの代わりにghq getを使用するだけでこの規約に沿ったクローンを自動で行ってくれます。
けど階層が深くなってディレクトリ移動が面倒と思ったならばghq listをfzfに渡してやれば即座に解決します。
motemen/ghq
ghqを使ったローカルリポジトリの統一的・効率的な管理について
tig
tigはgitのリッチなCLIクライアントです。
gitは非常に多くのリソースを持ち操作も多彩であるため、標準のgitコマンドでは面倒になる操作が多々あります。
fzfを使えばある程度は解決しますが、git logで表示されたコミットを順番にgit diffで眺めるといった用途には対応できません。
tigは起動するとgit logをカラーとグラフ付で綺麗に表示してくれます。
Enterを押せばカーソル上のgit diffを表示することが可能です。
豊富なビューの種類を備えており、現在表示しているdiffやカーソル位置にあるcommitに併せて表示切り替えと操作を縦横無尽に行えます。
個人的にgit statusで表示するステータスビューは非常に強力です。
git add, git reset, git checkoutの操作をtigのステータスビュー以上の速度で操作するのはちょっと想像できません。
しかし、反面今回紹介する中では最も複雑なツールといえるかもしれません。
キーバインドが身体に馴染むまでは、少々骨が折れると思います。
2-3. ポータブルな環境でいつでもパワフル
前節で多くのプラグインやツールの紹介をしてきましたが、これらの環境整備や設定ファイルは多岐に渡り、
手動ですべての設定を行うことは非常に困難です。
私は仕事用の端末とプライベート用の端末が完全に分かれていますし、家で構築した環境が職場で使用できなければ、
折角の環境設定を二重管理する必要が出てきてしまいます。これは非常によろしくない状況です。
dotfiles駆動開発
上記の問題を解決するための先人の知恵というものが当然あります。
CLIの設定はすべてテキストファイルで記載されています。
これはUNIXの思想でもある「効率より移植性を優先する」に基づいたものなのでしょう。
つまりバージョン管理と共有が容易であるということです。
インターネットで共有できるホスティングサービス(普通はGitHubを使用すると思います)上に、
CLIの設定ファイルをまとめたdotfilesリポジトリを作成しましょう。
あとは共有したい端末でgit cloneを行えば設定の共有は完了です。
zplug
設定ファイルの共有はdotfilesリポジトリを作成すれば完了しますが、
端末にインストールされているプラグイン/ツールに関しては別途プラグインマネージャで対応する必要があります。
私は心のShell師匠 @b4b4r07 さんが作成されているzplugをおすすめします。
ほかはAntigenやPreztoやzgenなどzshのプラグインマネージャは幾つかあるのですが、
プラグインだけでなくコマンド管理も可能な点や、起動が高速である点などの理由からzplugをチョイスしています。
zplugを導入すればCLIに依存しているツール類のインストールはすべて複数端末で同期されます。
なおzplugの設定自体はzshで記載されているため、dotfilesで共有できます。
zplug/zplug
おい、Antigen もいいけど zplug 使えよ
make
最後にデプロイの話です。dotfilesはホームディレクトリ直下にgit cloneしてきますが、
各設定ファイルはホームディレクトリ配下に読み取りに来るため、
そのままだと~/.dotfilesに配置されただけなので、
ホームディレクトリ配下にdotfiles内のファイルのシンボリックリンクを設定する必要があります。
これについてはいくつも方法があるのですがここは心のShell師匠 @b4b4r07 さんが提唱している方法に則って
makeコマンドで実行しています。
最強の dotfiles 駆動開発と GitHub で管理する運用方法
最後に
いかがだったでしょうか。CLIの魅力を少しでもお伝えできていたら幸いです。
