Edited at

唸れ!重装系ZSH!次世代のトレンドを体験せよ。

More than 1 year has passed since last update.


ZSH

突然だが、ZSHは標準シェルとして使っているだろうか?

自分はfishも試してみたが、-Uで設定が自動的に書き込まれるあたりが逆に気に入らなかった。

現在の自分の設定を見渡しにくいというのが印象的だった。

最初から出来上がっているので、シェルというよりもGUIアプリの様だった。普段使いでないマシン、例えばラズパイとかに入れると費用対効果が高くなるとは思うが、ZSHのように「漢の意地」をギュウギュウと詰め込んでいくような代物ではなかった。

だからやっぱりZSHなのだ。

あくまで「インタラクティブ」なシェルの話をしたい。だからBashやボーンシェルは今回は論外なのだ。(インタラクティブとは「双方向な」の意でシェルに関しては対話型という意で使われるが、再描画可能なシェルこそ、よりインタラクティブだと言える。)


現在のトレンド

ZSHの中でも潮流がある。現在のトレンドはみな起動時間をそれはもうF1レーサーのようにコンマ1秒でも早くしようというものなのだ。

だがそんなことに意味があるのだろうか?

例えば一度のコマンド入力で補完の恩恵を受けることで、コンマ何秒の差なんて埋まってしまうし、補完が効けばそのCLIの使い方を何にも覚えてなくてもなんとでもなってしまう。

ZSHの究極はいわばダメ人間養成マシーンではないだろうか?

ぶっちゃけてしまうが、俺はZSHの起動に最大30秒は待てる。

実際に俺のZSHは最近の改修をするまで20秒かかっていたが、現在は15秒程度で落ち着いている。

そのへんはこれから詳しく触れるが、oh-my-zshからzplugに乗り換えた影響もある。

乗り換えた理由はスピード狂たちの影響ではなく、この重戦車設定を再現可能な資源にしたかったからだ。


oh-my-zshの簡単な説明

実はoh-my-zshからzplugに「乗り換えた」のだが、ないがしろにするべきものではないので説明しておく。

近頃、このフレームワークはディスられがちだが、単純に悪いと評価するのは安直である。

第一にほかのzshフレームワークを使っている人もoh-my-zsh内のプラグインを何らかの形で利用しているケースがある。現に今の自分はzplugでoh-my-zsh由来のプラグインを一部利用している。

よく話題に上がるのは、「重い」、「管理体制が古い」とかだ。

まず、「重い」について。

ZSHルーキー達すらもズルムケZSHユーザー設定にするほど多くの機能や設定をしてくれているからだ。そこで時間がかかっている。

つまり、起動速度と機能量はトレードオフなのだ。

多少の工夫(あれこれキャッシュするなど)でわずかに起動を早く出来るが、原則トレードオフだ。つまり軽い(起動が早い)ということは機能拡張していないというだけなのだ。

ZSHに代わって俺はこう言い訳したい。

重い武器を多く積載する重装車に加速度(あくまで初動)を求めるのは酷だと。

そして「管理体制」は確かに古い。

どこかで読んだ情報だが、内包されるプラグインの中には何年もメンテされていないものが多い。

だが、単品ではgithubで公開されていない魅力的なプラグインも多数在籍しているのでこれから紹介するzplugでおいしいとこ取りしようではないか。

前置きが長くなったがそれではoh-my-zshについて説明する。

まずoh-my-zshの基本スクリプトがいい感じにsetoutとかzstyle辺りの設定を弄ってくれて「ZSHらしい」感じにしてくれる。

その上でいくつか用意されたテーマの中から一つを選ぶ。

さらに用意された中から必要なだけプラグインを選ぶ。

選んだものはzshrc内で宣言する。

もちろん、テーマもプラグインもあとからサードパーティのものを足してやることができる。

俺に言わせりゃ、起動速度などなんの問題もなかった。

しかし外部プラグイン(oh-my-zsh以外のプラグイン)は~/.oh-my-zsh/custom/pluginsにクローンするなりして使うことになり、複数環境で共有するためmackupコマンドを使ってcustomディレクトリを共有するなどの工夫が必要だった。

そのうえ、新しいプラウインの導入に一手間感じており、新しめのzshプラグインマネージャーに魅力を感じていた。


zplug

これが素晴らしいと感じたのは、どんなgitリポジトリも大抵設定次第でプラグイン扱いできることだ。

リポジトリの直下にご丁寧に*.plugin.zshみたいなファイルがない荒くれリポジトリや本来zshプラグインでないリポジトリもプラグインとして扱える。

gistだって扱えるので参考にしたいコードがあれば、それを起動時に読み込ませることができる。

コマンドとしても取って来させることができるので、例えば依存関係さえなければ、

githubやgist上のpythonやrubyのコードをPATHの通ったところにコマンドとして配置させることもできる。


具体的な設定

まずzplugはいくつかインストールの仕方がある。

Macユーザーの場合はbrewで取ってきて

export ZPLUG_HOME=/usr/local/opt/zplug

source $ZPLUG_HOME/init.zsh

を書いておけばいい。


テーマ

まず、テーマはこれ一択だ。zshのテーマで一番重い。

重いというのは「良い」ことと捉えよう。

人間よりも処理の早いマシンがそのマシンパワーで我々人間が浪費する時間や思考リソースを先回りして減らしてくれているということなのだ。

zplug "bhilburn/powerlevel9k", use:"powerlevel9k.zsh-theme", as:theme, if:"source ~/.powerlevel9k"

ifタグは条件を満たせばそのzplugをインストールするということなのだが、「評価している」ということは「実行している」ということなので自分みたいにワンライナー厨はこういう使い方もする。

.poserlevel9kには、それはもうヘビーなテーマ設定が書き込まれている。


プラグイン from github

次はプラグインだ。

これは人によって使うものは全く違うが王道入れるべきだ。

zplug "zsh-users/zsh-autosuggestions"

zplug "zsh-users/zsh-syntax-highlighting"
zplug "zsh-users/zsh-completions"
zplug "RobSis/zsh-completion-generator", if:"GENCOMPL_FPATH=$HOME/.zsh/complete"
zplug "Tarrasch/zsh-functional" # each map filter fold
zplug "willghatch/zsh-hooks"
zplug "unixorn/warhol.plugin.zsh" # ansi
zplug "mollifier/zload"
zplug "b4b4r07/enhancd", use:"init.sh"

俺のプラグインもくれてやる。

zplug "GeneralD/zsh-brew-file-wrapper", if:"[[ $OSTYPE == *darwin* ]]"

zplug "GeneralD/zsh-aliases-colorls"
zplug "GeneralD/zsh-completion-gibo"
zplug "GeneralD/zsh-without-etcdir"

どれも個人的な流儀を押し付ける暴れん坊だ。

zsh-brew-file-wrapperはbrew fileというMacのbrew fileのラッパーだ。

興味があればbrew fileの公式にあるラッパーの説明を読んで欲しい。

そのラッパーとやらを適応するだけのライブラリだ。

俺のMacにはAppStoreかbrewかbrew caskでインストールしたもの以外一切入っていない。

もしそこにないアプリは自分のプライベートリポジトリにfomulaやcaskを足して管理している。

つまりbrew fileを通じて複数台のMacや新規Macでさえも環境を共通化してしまえる。

脱線した。

colorlsはlsをアイコン付きで表示するruby実装のコマンドだが、それを使いやすくする俺のエイリアスを押し付けるためのZSHプラグインがzsh-aliases-colorlsだ。

zsh-completion-giboはgibo(.gitignore生成装置)の補完を効かせるためのもの。

zsh-without-etcdirは/etc以下のzsh起動スクリプト類を読み込まないバージョンのzshを使ってない場合、それを強制的にインスコして使わせようとする、それはもう押し付けがましいやつだ。

これは存続の環境のためにというよりも新しい環境でこのプラグインが読み込まれた場合を想定している。


プラグイン from gist

zplug "GeneralD/c11b656364a38e0f67182411a0ee14b8", from:gist, use:"enable-cdr.zsh"

zplug "GeneralD/bd3a10afa9d37e9efe7f5af288675ca6", from:gist, use:"partial-search-history-with-arrow-keys.zsh"
zplug "GeneralD/b3b7c2290db941ad5b2a5e3eb50e1e9e", from:gist, use:"expand-or-complete-with-dots.zsh", if:"WAITING_DOTS='···'"

このようにすればgistに書いたコードもプラグイン扱いできる。

上から、


  • cdrを有効化する。

  • コマンド入力中、上下キーで続き部分だけを履歴から補完。

  • 補完中のドット「...」表示。


プラグイン from oh-my-zsh

oh-my-zshを卒業したとしてもその中に使いたいプラグインはいくつもある。

そんな場合、zplugにはoh-my-zsh在籍のプラグインを取り込む方法まで用意されている。

zplug "plugins/colored-man-pages", from:oh-my-zsh

zplug "plugins/colorize", from:oh-my-zsh
zplug "plugins/command-not-found", from:oh-my-zsh
zplug "plugins/composer", from:oh-my-zsh
zplug "plugins/docker", from:oh-my-zsh
zplug "plugins/emacs", from:oh-my-zsh
zplug "plugins/extract", from:oh-my-zsh
zplug "plugins/gem", from:oh-my-zsh
zplug "plugins/git", from:oh-my-zsh
zplug "plugins/github", from:oh-my-zsh
zplug "plugins/gitignore", from:oh-my-zsh
zplug "plugins/gnu-utils", from:oh-my-zsh
zplug "plugins/go", from:oh-my-zsh
zplug "plugins/gpg-agent", from:oh-my-zsh
zplug "plugins/heroku", from:oh-my-zsh
zplug "plugins/httpie", from:oh-my-zsh
zplug "plugins/man", from:oh-my-zsh
zplug "plugins/osx", from:oh-my-zsh
zplug "plugins/pip", from:oh-my-zsh
zplug "plugins/pod", from:oh-my-zsh
zplug "plugins/wp-cli", from:oh-my-zsh
zplug "plugins/xcode", from:oh-my-zsh
zplug "plugins/z", from:oh-my-zsh

自分はもっとたくさんのプラグインをoh-my-zshで利用していたが、今回の以降で補完が被ってて不要なものを取り除いた結果、これだけ残った。

oh-my-zshを使っている人は下記のコマンドでzplugの設定に変換できる。

これの出力を.zshrcに貼り付けると、oh-my-zshの使用プラグイン設定部分をzplugにお引越しできる。

for i in $plugins; do; echo zplug \"plugins/$i\", from:oh-my-zsh; done

ちなみに少しでも無駄なロードは省きたい、環境によってプラグインが利用できない状態の時はロードさせたくないのなら、こういう悪あがきもありだろう。多少は時間を節約できる。

zplug "plugins/colored-man-pages", from:oh-my-zsh

zplug "plugins/colorize", from:oh-my-zsh, if:"which pygmentize 1>/dev/null"
zplug "plugins/command-not-found", from:oh-my-zsh
zplug "plugins/composer", from:oh-my-zsh, if:"which composer 1>/dev/null"
zplug "plugins/docker", from:oh-my-zsh, if:"which docker 1>/dev/null"
zplug "plugins/emacs", from:oh-my-zsh, if:"which emacs 1>/dev/null"
zplug "plugins/extract", from:oh-my-zsh, hook-load:"alias -s {tar,gz,tgz,bz2,tbz,tbz2,xz,txz,zma,tlz,lzma,Z,zip,war,jar,rar,7z,deb}=extract"
zplug "plugins/gem", from:oh-my-zsh, if:"which gem 1>/dev/null"
zplug "plugins/git", from:oh-my-zsh, if:"which git 1>/dev/null"
zplug "plugins/github", from:oh-my-zsh, if:"which hub 1>/dev/null"
zplug "plugins/gitignore", from:oh-my-zsh, if:"which git 1>/dev/null"
zplug "plugins/gnu-utils", from:oh-my-zsh
zplug "plugins/go", from:oh-my-zsh, if:"which go 1>/dev/null"
zplug "plugins/gpg-agent", from:oh-my-zsh, if:"which gpg-agent 1>/dev/null"
zplug "plugins/heroku", from:oh-my-zsh, if:"which heroku 1>/dev/null"
zplug "plugins/httpie", from:oh-my-zsh, if:"which http 1>/dev/null"
zplug "plugins/man", from:oh-my-zsh
zplug "plugins/osx", from:oh-my-zsh, if:"[[ $OSTYPE == *darwin* ]]"
zplug "plugins/pip", from:oh-my-zsh, if:"which pip 1>/dev/null"
zplug "plugins/pod", from:oh-my-zsh, if:"which pod 1>/dev/null"
zplug "plugins/wp-cli", from:oh-my-zsh
zplug "plugins/xcode", from:oh-my-zsh, if:"[[ $OSTYPE == *darwin* ]]"
zplug "plugins/z", from:oh-my-zsh


ZAW

zaw、めちゃくちゃ使ってるので自作プラグインも多数作ってきた。

正直、ひっそりとgithubにオープンソースで置いておく分には良い。

でも、あまり人に教えたくない。そんなプラグインを紹介する。

zplug "zsh-users/zaw"

zplug "GeneralD/zaw-src-nerd-icon", on:"zsh-users/zaw", defer:2
zplug "GeneralD/zaw-src-directory", on:"zsh-users/zaw", defer:2
zplug "GeneralD/zaw-src-bitbucket", on:"zsh-users/zaw", defer:2
zplug "GeneralD/zaw-src-github", on:"zsh-users/zaw", defer:2
zplug "GeneralD/zaw-src-ghq", on:"zsh-users/zaw", on:"Tarrasch/zsh-functional", defer:2
zplug "GeneralD/zaw-src-package-managers", on:"zsh-users/zaw", defer:2
zplug "GeneralD/zaw-src-unity-launcher", on:"zsh-users/zaw", on:"Tarrasch/zsh-functional", on:"sttz/install-unity", defer:2
zplug "sttz/install-unity", as:command, use:'(*).py', rename-to:'$1', if:"[[ $OSTYPE == *darwin* ]]", hook-build:"pyenv versions --bare 2>/dev/null | gsort --version-sort --reverse | egrep '2.[0-9]+.[0-9]+' | head -n 1 > $ZPLUG_REPOS/sttz/install-unity/.python-version"

それぞれがっつりREADME.mdに書いてあるからあとはそれ読んでって感じ。

ここでその魅力に触れてしまうとインストールが殺到してしまい優位性が崩れかねない。(過大)


oh-my-zsh依存症

oh-my-zsh依存症と言う時点でoh-my-zshからの「乗り換え」を想定した話になってしまい、主題と論点がずれてしまうのだが、参考になる人もいると思うので進めさせていただく。

zplugなどのプラグインマネージャーを用いればoh-my-zshのプラグインやテーマを確かに利用できる。しかしzplugやantigen、zgenはプラグインマネージャーであって先ほど説明したoh-my-zshの基本スクリプトのような便利設定の「押し付け」は行わない。

俺はzshルーキーの頃、どう設定すれば使いやすいかわからないから、とりあえずoh-my-zshでスタートを切り、ルーキー卒業後はプラグインマネージャーに乗り換えた。

補足すると、この設定がすごく良いとかじゃなくて、この流れで長年使うとエイリアスやその機能がある前提で手が動いてしまい、zshデフォルトの機能だと思っていたものがそうでなかったりするからだ。

まさにoh-my-zsh依存症とは俺のことだったんだが、以下のような設定を書けばだいぶoh-my-zshの標準に近く。

setopt auto_cd

setopt auto_param_keys
setopt auto_param_slash
setopt auto_pushd
setopt brace_ccl
setopt correct
setopt correct
setopt extended_glob
setopt extended_history
setopt hist_ignore_dups
setopt hist_ignore_space
setopt list_packed
setopt magic_equal_subst
setopt mark_dirs
setopt nolistbeep
setopt prompt_subst
setopt pushd_ignore_dups
setopt share_history

zstyle ':chpwd:*' recent-dirs-max 500
zstyle ':completion:*' matcher-list '' 'm:{a-zA-Z-_}={A-Za-z_-}'
zstyle ':completion:*:default' menu select=1
zstyle ':filter-select' case-insensitive yes
zstyle ':filter-select' extended-search yes
zstyle ':filter-select' hist-find-no-dups yes
zstyle ':filter-select' rotate-list yes

export HISTFILE=~/.zsh_history
export HISTSIZE=1000
export LISTMAX=0
export SAVEHIST=100000

これに、先ほど触れた

zplug "GeneralD/bd3a10afa9d37e9efe7f5af288675ca6", from:gist, use:"partial-search-history-with-arrow-keys.zsh"

zplug "GeneralD/b3b7c2290db941ad5b2a5e3eb50e1e9e", from:gist, use:"expand-or-complete-with-dots.zsh", if:"WAITING_DOTS='···'"

のプラグインなどもあるとそれはもうオーマイZSH。

ちなみにoh-my-zshを使うよりも、上記の設定+プラグインマネージャー(今回はzplug)の方がわずかに起動が早い。


zplug使用上の注意

基本的に公式を一度見てもらったほうがいいが、上で触れたzplugのパスを通す記述部分に始まり、最後は

zplug check --verbose || zplug install

zplug load

を書くこと。

これで必要なインストールとロードが行われる。

公式の書き方だとインストール毎にプラグインが50あれば50回Y/nを訊かれるので俺のオートマ方式採用が正義。自動化セヨ。


キーバインド

zawはキーバインドを設定してなんぼ。

俺の自作zawプラグインも全部インスコしたならとりあえずこれでもテンプレに

bindkey '^z1' zaw-git-branches

bindkey '^z2' zaw-git-files
bindkey '^z3' zaw-git-files-legacy
bindkey '^z4' zaw-git-log
bindkey '^z5' zaw-git-recent-all-branches
bindkey '^z6' zaw-git-recent-branches
bindkey '^z7' zaw-git-reflog
bindkey '^z8' zaw-git-status
# bindkey '^z9' zaw-
bindkey '^z0' zaw-aliases
bindkey '^za' zaw-applications
bindkey '^zb' zaw-homebrew
bindkey '^zc' zaw-brewcask
bindkey '^zd' zaw-dirstack
bindkey '^ze' zaw-ssh-hosts
bindkey '^zf' zaw-functions
bindkey '^zg' zaw-rubygem
bindkey '^zh' zaw-history
bindkey '^zi' zaw-github-starred
bindkey '^zj' zaw-ghq
bindkey '^zk' zaw-ghs
bindkey '^zl' zaw-bitbucket
bindkey '^zm' zaw-mac-appstore
bindkey '^zn' zaw-nerd-icon
bindkey '^zo' zaw-open-file
bindkey '^zp' zaw-pypi
bindkey '^zq' zaw-cdd
bindkey '^zr' zaw-cdr
bindkey '^zs' zaw-screens
bindkey '^zt' zaw-tmux
bindkey '^zu' zaw-unity
bindkey '^zv' zaw-install-unity
bindkey '^zw' zaw-widgets
# bindkey '^zx'
bindkey '^zy' zaw-bookmark
bindkey '^zz' zaw
bindkey '^z^z' zaw
bindkey '^z[' zaw-programs
bindkey '^z]' zaw-process
bindkey '^z<' zaw-command-output
bindkey '^z>' zaw-commands


終わりに

圧倒的な我流の押し付け、なにより起動が重い。

だが起動に時間がかかる秘密兵器ほど強いものだ。

俺は知っている。多くのエンジニアがスロースターターだということを。

スロースターター、結構ではないか。


追記

上でさらっと述べた「何にも覚えなくていい」環境についてだが、

以下のコマンドを有効活用していただくとその感覚を体験していただけると思う。

gencomp まだ補完が効かないコマンド

zload $GENCOMPL_FPATH/_*

ハイフンで始まる部分の補完のみだが、何も補完が効かないよりは非常に心強いと思う。

これを使うには上で紹介したmollifier/zload と RobSis/zsh-completion-generatorが必要だ。

gencompで補完関数を生成しファイルに書き出し、zloadで補完関数のリロードだ。

もう一つ追記するとすれば、ワンライナーで簡単に設定を書くためifタグを用いたが、hook-loadタグで該当プラグインロード後に実行されるコマンドを指定できる。しかしGENCOMPL_FPATHの設定はzsh-completion-generatorプラグインのロード前にしないといけないので、この場合はhook-loadタグではいけない。