開発効率向上 tips vol.1 -ターミナル・シェル編-

最近弊社でも大学生のエンジニアインターンが増えてきて,彼らの成長のために,開発にあたっての知見を効率的に共有する手段を色々と模索している.こういった知見は再利用性も高く,社内に閉じている必要もまったくないので,こうしてブログに書き溜めていくことにしてみようかと思う.第一弾として,端末エミュレータ上でのオペレーションの効率化について記す.

ソフトウェアの開発をやっていると,なんだかんだで端末エミュレータというインタフェースの上で,シェルを使ってオペレーションを行うことが多い.開発に用いるマシンの OS はたいてい macOSLinux の何かしらのディストリビューション(Ubuntu が多いか?)だし,プロダクション環境での運用は Linux を前提することが多いだろう.Docker の普及により,Linux への理解の重要性はますます高まっている.こういった Unix-like OS の上で効率的なオペレーションを行う上で,コマンドライン・インタフェースを介したオペレーションへの習熟は避けられない.

とはいえ,CLI 上でのオペレーションの学習曲線はかなり急峻で,慣れるまでに時間が掛かるし,慣れていないうちから使いづらいインタフェース上で長時間作業をさせられるのは苦でしかない.そこでこの記事は,(主に Zsh に関する)様々な tips を紹介することで,環境構築の手助けをし,開発効率向上の一助となることを期待して書かれている.

以下では,macOS 上でのオペレーションを念頭に解説する.Linux を使っている人は,好きで使っていると思うので,ある程度自分でなんとかしてほしい.

フォントについて

コマンドやプログラムは1文字違うと動かないので,読み間違いをしないよう,見やすい大きなフォントと背景を心掛ける.Mac の Terminal.app は,デフォルトでは背景色が白になっていて,随分と文字が小さい(11 pt.)が,あれは目が痛くなるし読みにくいのでやめた方がいいと思う.たぶん背景は黒の方がいい.フォントは,僕は Menlo の 15 pt. に設定しているが,もしかするともうちょっと見やすいフォントがあるかもしれない.(最近 id:hiroqnMonoid を導入していた.名前もいい.)

フォントは,"Preferences" (Cmd + ,)の "Profile" の中にある "Font" セクションの "Change..." から変更できる.

初期の設定だと,14 pt. の次の大きなサイズが 18 pt. になっていて細かい調整が効かないが,以下のようにして任意のフォントサイズを追加できる.各々のディスプレイなどの環境や視力に合わせて変えてほしい.

Edit sizes...

adding 15 pt.

Fixed List のチェックを外すのを忘れぬよう.

ターミナルでよく見る色は,{white, black, red, green, blue, magenta, yellow, cyan} × {normal, bright} の16色.これらの色を好みに(視認性を担保しつつ!)カスタマイズするのもいいだろう.

ログインシェルについて

(たぶん「ログインシェル」という用語で大丈夫だと思うのだけど,UserShell という表記も見るのであまり自信がない.間違ってたら優しく教えてください……)

端末を開いて,初めに立ち上がるシェルは,何も設定していなければ多分 bash (/bin/bash) になっていると思うが,(個人的な意見としては,)特に強い理由がなければ Zsh に変更した方がいいと思う.Zsh は言うなれば bash の上位互換みたいなもので,具体的な優位性としては,

  • Tab キーによる補完が優秀
    • cd /u/l/b と入力した状態で Tab キーを押すと cd /usr/local/bin と展開される
    • approximate 補完を使うと多少の typo を許容しつつよしなに補完してくれる
    • 補完の際に,補完対象のカテゴリ (ファイル,Git のリビジョン,PID など)のカテゴリ分けがキレイに表示されて便利
    • scp とかでリモートのサーバにあるファイルを手元に持ってこようとするときにも,リモートのパスを補完してくれる(!)
  • ファイルを一括で rename したいときに zmv が便利すぎる
    • noglob zmv -W hoge/*.ext ./hoge-*.ext みたいなことができてすごい
  • とにかく設定項目が多くて使いこなすのが無理

と,いろいろすごい.特に補完が強力なのは大事で,どうせ人間はタイプミスをするし,一言一句間違えずにキーボードを正確に打つことは意外と神経を使うので,とにかく Tab キーを連打しまくって楽をするように心掛けよう.本質的じゃない部分で消耗するのは本質的でない.(本質的に.)

ログインシェルを変更するには,chsh コマンドを使う.

$ chsh -s $(which zsh)

$(command) というのは command substitution といって,中に書いたコマンドの標準出力への出力内容を,コマンドの引数に渡す文字列として置き換える,という機能である.(バッククォートで囲んでも同じ挙動をするのだが,この記事を Markdown で書いている関係上,表記が大変なのでこちらを使う.) chsh でログインシェルを変更したい場合,そのシェルのパスが /etc/shells に書かれている必要がある./bin/zsh とかなら最初から記載されていると思うが,自分で他のシェルをインストールしてログインシェルに設定したい場合などには,追記するのを忘れないこと.

インタラクティブシェルとして使いたい場合の Zsh の設定ファイルは,だいたい ~/.zshrc に書くことになっている.初回起動時に,ウィザードに従うと最低限の .zshrc を生成してくれるはずだ.

補完機能を有効にするには,以下のように書く.

autoload -Uz compinit; compinit

また,approximate completion を有効化するには,以下のように書けばよい.

zstyle ':completion:*' completer _complete _approximate

他にも便利な completer があるが,詳細は 20.4 Control Functions を参照のこと.

あと,補完の際に大文字小文字を区別しないようにしておいた方が精神衛生上よい.

zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'

プロンプトについて

プロンプトとは,コマンドを打つときに左に出ているアレのこと.プロンプトに何を表示しておくべきかという議論はいろいろありそうだが,個人的には以下の要件を満たしていれば十分かなと思っている.

ユーザ名やらホスト名が必須かというと,ローカルの端末で一人で開発する際にはそんなに重要じゃないのでは……??と思っているが,まぁ普通は表示すると思う.

カレントディレクトリが見られる

カレントディレクトリはさすがに常に表示しておいた方がいい.コマンドを実行する際に,現在いるディレクトリ-aware であるべき場面は多いし,いちいち pwd を叩きたくない.

VCS (Git などのバージョン管理システム) の情報が見られる

VCS,と言っても Git しか使わないのだが,の情報が常に見られると便利,というか,今いるブランチを確認するためにわざわざ git branch を叩きたくなさすぎる.Zsh はそういった用途のために vcs_info というものを提供している.

視認性が高い

「視認性が高い」という部分は,もしかすると人によって好みがあると思うが,僕の場合はプロンプトに2行,上に1行の空行を入れて広めにスペースを取り,カレントディレクトリ,vcs_info,ユーザ名・ホスト名ではそれぞれ色を分けている.典型的なシェルは,標準出力・標準エラー出力の直下にプロンプトが表示されるが,あれはあまりに見にくいと思う.一度のコマンド実行あたりに取るスペースが広いので,一画面に収まる情報量は少なくなってしまうが,上の方の出力結果が見たい場合は,スクロールすればいいと信じている.

直前のコマンドの終了ステータスがわかる

コマンドを実行して,成功したつもりでいたら実は失敗していた,という経験があるかもしれない.エンジニアたるもの出力されたメッセージを見ずに成功と信じるのはご法度だが,標準出力と標準エラー出力が両方端末に接続されていると,一見見分けがつかないので仕方ないといえば仕方ない.そこで,Unix の終了ステータスをうまく活用する.直前の終了ステータスが 0 でなかった場合,つまり正常終了しなかった場合に,プロンプトの色を変えてやると,正常と異常がひと目で見分けられる.

設定例

以上を勘案した上で,僕のプロンプトは以下のようになっている.

autoload -Uz vcs_info
zstyle ':vcs_info:*' formats '(%s)-[%b]'
zstyle ':vcs_info:*' actionformats '(%s)-[%b|%a]'

function precmd() {
    psvar=()
    LANG=en_US.UTF-8 vcs_info
    [[ -n "$vcs_info_msg_0_" ]] && psvar[1]="$vcs_info_msg_0_"
}

PROMPT="
%F{cyan}[%~]%f %1(v|%F{green}%1v%f|)
%(?.%F{yellow}%}.%F{magenta})%n@%m%f $ "

% から始まる文字列は色々に展開されるのだが,詳しくは 13 Prompt Expansion を参照してほしい.

キーバインドについて

Vimmer の僕でも,単行編集の際にはさすがに Emacs キーバインドに優位性があると思っているので,以下のように設定している.

bindkey -e

これで Emacs キーバインドが有効になる.主要なキーバインドを以下に挙げる.macOS の欄に丸が付いているものは,Cocoa Text System でもサポートされているので,普段 Mac を使っていて普通に接するインタフェースでも利用できる.(事実この記事はこれらのキーバインドを駆使して書かれている!)

キー 挙動 macOS
C-a 行頭に移動 o
C-b 1文字左に移動 o
C-d delete o
C-e 行末に移動 o
C-f 1文字右に移動 o
C-h backspace o
C-k 行末まで削除 o
C-n 1つ次のコマンド o
C-p 1つ前のコマンド o
C-u 1行削除
C-w 1単語削除
M-b 1単語右に移動
M-d 単語を削除
M-f 1単語左に移動

C-y でヤンク(ペースト)できたりとか,他にもある.

meta キーってどうやって打つんだと思うかもしれないが,最寄りの Emacs ユーザに尋ねてほしい."Preferences..." (Cmd + ,) -> "Profiles" -> "Keyboard" の中に Use Option as Meta key というものがあるので,僕はこれを有効化している.

履歴検索

エンジニアの三大美徳に「怠惰」というものがあるが,面倒な作業を面倒だと思わずに愚直に行ってしまうのは確かによくない.一度打ったコマンドを検索するのに,キーを連打しまくったりしていないだろうか.C-r を押すと,今までに実行したコマンドの履歴からインクリメンタルに検索できる.また,M-. を押すと,直前のコマンドの最後の引数が入力される.

Zsh の機能の中だと,history-search-end がおすすめ.Zsh はデフォルトでも,M-n 及び M-p で,現在の入力内容に基づく履歴検索の機能を提供しているが,以下の設定をしておくと,例えば git re まで打ってから M-p を(何度か)打つと,履歴から git re から始まるもののみ (git reset ...git rebase ... など) を表示してくれ,かつカーソルを行末に移動してくれる.行き過ぎた場合には,M-n で戻れば良い.

autoload -U history-search-end
zle -N history-beginning-search-backward-end history-search-end
zle -N history-beginning-search-forward-end history-search-end
bindkey "^[p" history-beginning-search-backward-end
bindkey "^[n" history-beginning-search-forward-end

僕は C-r で開始できる reverse-i-search の挙動が好みでないので,peco を使って,履歴をいい感じに検索できるようにしている.これに関しては,"peco zsh history" あたりで検索すると,いくらでも情報が出てくるだろう.

その他

auto_pushd

ディレクトリ構造の深海に潜っているとき,間違えて引数無しで cd を実行してしまい,ホームディレクトリまで一気に浮上してしまって戻れない,という経験はおありだろうか.auto_pushd はそんなつらい問題を解決してくれる.

setopt auto_pushd

pushd を実行すると,現在のディレクトリをスタックに積み,popd を実行すると,スタックから pop してきて,そのディレクトリに移動する.auto_pushd は読んで字の如く,cd 時に pushd を自動的に行ってくれる素敵なオプションである.間違ってホームディレクトリに移動してしまっても,popd 一発で元の場所に戻れる.

僕は面倒なので alias p='popd' と書いているけど,これはやり過ぎかもしれない.

cd 時に ls

cd したらだいたい ls するので,自動で行うようにしている.このせいで画面の内容が流れていくのが余計に速くなっているが,見たければスクロールすればいいと割り切っている.

function chpwd() {
  if [ `ls -Al | wc -l` -eq 0 ]; then
    echo "\n\nempty directory";
  else
    echo "\n"
    ls
  fi
}

zsh-syntax-highlighting

zsh-syntax-highlighting を入れると,コマンドを入力している途中でもハイライトを行ってくれるようになる.これを入れると,今から実行しようとしているコマンドがそもそも $PATH にあるのかないのかが,実行する前に以前に色でわかるので,typo に対するフィードバックが非常に速い.

macOS をお使いの場合は,Homebrew を使ってインストールできる.設定手順がインストール後に表示されるので,参照されたい.

$ brew install zsh-syntax-highlighting

終わりに

当然ながら,環境構築ばかりやっていても成長はしない.しかし,普段の開発の効率を向上させるために,ある程度質の良い環境を用意しておくことも大事ではあるし,少なくとも好んで劣悪な環境の上に立って仕事をすることはないだろう.

こういった環境の整備は,往々にして一度手を付けるとやめられなくなってしまうことが多い.個人的にも,何度 .zshrc.vimrc を編集して朝を迎えたかわからない.読者諸兄においては十分に注意されたい.