zshで究極のオペレーションを

第1回zshで広がる世界

zshの力

図1 tetrisプレイ中
図1 tetrisプレイ中

zshはシェルである。シェルはもちろんキーボード入力されたコマンド行を解釈し、必要なコマンドを必要な引数とともに起動することを主な仕事とするソフトウェアである。単なるシェルなのだが、zshには他を圧到する比類なき機能がある。その一端を印象づける一つの例に、zshで実装されたテトリスがある図1⁠。

もちろんこれは、お遊び機能の例で実際の日常作業をこれで進めるわけではないが、潜在的に備えている機能がどれほどのものかが分かる好例である。

zshは、sh(Bourne Shell)をベースとし、ksh、csh(tcsh⁠⁠、bashの優れた機能をアイデアとして取り込み、なおかつ作業効率を高める独自の機能を登載したまさに至高のシェルである。しかしながら超高機能・多機能であるがゆえに全容を掴むのが難しい。付属の英文マニュアルはしっかりしているものの、簡潔な仕様記述がされているのみなので具体的な挙動を知るには各項目について各自実験してみなければならない。

かつてUnixが高額ワークステーションでしか動かなかった時代、シェルの効率的な使い方などは先輩から教わったりすることでぐんぐん身に付けることができた。最近ではむしろ廉価なPCで個人ベース動かせる Unixが多く、情報収集も1人で行なわなければならないことが多い。

図2 zshの本
図2 zshの本

今回技術評論社から発刊されたzshの本は、zsh利用時のよき伴侶となるようsh/zshの動きのしくみに近いところから、典型的な利用例までを収めることを試みた一冊である。本書の紹介に入る前に筆者のシェルとの関りについて振り返ってみたい。

シェル遍歴

筆者が最初に触ったUnixシェルはNEWS-OSで動くtcshだった。そのときはMS-DOSにぞっこんで、実はあまり見向きもしなかった。あとから、⁠あのとき使っていたのはtcshだったのか」と気付いた程度で、最初はUnixとはなんと不便でそっけないものかと感じていたに過ぎなかった。

学部4年で研究室に配属され、そのあとは新しい知識がどんどん入り結局はtcshにぞっこんになった。tcshの内部コマンド complete を使って普段使うコマンドのオプション補完を登録しまくるなど、カスタマイズに明け暮れる日々を送っていたのだが、設定に凝りたい気持ちをくじくような不便さがあった。問題は複雑だが、単純化した例で示すと、たとえば ~/.cshrcにこんな類の記述をして悩む。

if ( $?foo != 0 && $foo == "bar" ) then
  処理イロイロ
endif

処理もろもろ
  :
  :

気持ちとしては、⁠変数 foo の値が "bar" のときは…」という条件を設定したいのだが、cshは無情にもこうのたまう。

foo: Undefined variable.

cshでは未定義の変数を参照するとエラーになる。定義があるかを調べるために$?foo を使う、というところまではcshユーザの常識(?)だが、同じ行に構文的に $foo があるとエラーになる。結局これは ifを二重にすれば解決するのだが問題は、スクリプトでエラーが出るとその後の処理が止まってしまう点と、さらにどこで止まったのかの情報が得られない点にある。上記の例のエラーメッセージなら $foo というところでエラーが出ていると分かるが、そうでない場合はどこでエラーが出ているかを探すのに無駄に時間を費やすことになる。

さらにもうひとつ、tcshファンでありながらどうにも満足できない点に、複数行に渡る構文foreachなど)を打つと、それを ^P で呼び出せないことがあった。長い構文は複雑だからこそ試行錯誤したいのに、foreach 構文を打ったあとに ^P を押しても、foreach の行しか戻ってこないのが泣けてきた。

基本が便利なだけに、どうしても不満点が気になった。そして、 tcshでは根本的改善は望めないらしきことがだんだん分かってきたあたりで他のシェルへの乗り換えを検討し始めた。bashも試した。だが、当時のbashはtcshと比べても補完機能が圧到的に劣り(今は違う)、すぐに候補から消えた。そして、最終的に出会ったのがzshだった。とはいえ、すべて気に入った訳ではなくいくつか気になる点があった。

zsh乗り換え当初の不満

csh(tcsh)に慣れていたことに起因するものがほとんどだが、 zshを使い始めたときにはちょっとした抵抗を感じた。

文法がsh

当たり前だが、shの文法は最初どうにも馴染めなかった。 cshの文法に親しんだ目からみたshは、どこか冷徹で、 初心者を受け付けない雰囲気をなぜか感じた。if を閉じるのが fi というのにどういうわけか厳しさを感じたり、 そもそも if の後ろの条件の書き方もよく分からなかった。

最終的には、キーワードの違いに関しては 慣れてしまえばどうということのないことと当然気付き、 条件式の書き方に関しては「条件」と捉えるよりも ⁠コマンド」と捉える方がすっきり理解できることが分かった。 ようは ifwhile の後ろには何でもいいからコマンドが書けて その実行が成功したら真、そうでなければ偽、というだけのことで、 たまたま比較演算子の働きをするコマンドが [ なのだと。 それが分かってからはむしろcshのときより書きやすく感じた。

単語移動が / で止まらない

実はシェルのキーバインドはいわゆる「ダイヤモンドカーソル⁠⁠ にしている。tcshでもそうだったし、Emacsでも。ダイヤモンドカーソル とは上下左右移動をそれぞれ C-eC-xC-sC-d に割り当てるもので、WordStarに由来すると言われているキー配列である(実際には自分用にカスタマイズしたVzエディタの定義を踏襲したものにしている⁠⁠。

この配列にこだわる理由は、単語移動を頻繁に使うからで デフォルトのキー配列にある ESC-bESC-f では連打が素早くできず、不採用の烙印をここで押した。単語移動は C-aC-f に割り当てているので、連打して素早く移動するためによく使っている。

さて、シェルの作業では長いファイル名を打つことがしばしばあり、 直前に打ったファイル名を微修正することもよくある。そのときに、 単語移動をするとzshのデフォルトではファイル名全体を1単語と みなすので、真中あたりに行くには文字移動で行かねばならず、 もどかしい。何とかならないものか。

これは簡単に解決した。zshのシェル変数 WORDCHARS にデフォルトで定義されている文字列から `/' を削るだけで直った。

もっとも、zshに馴染んだ今ではこれが最善策ではないと感じる。

  • そもそもキー連打で移動するのは賢くない。 C-r のインクリメンタルサーチで / や、移動したい所の文字列を打てば一瞬で確実にそこに移動 できる。
  • WORDCHARS の設定だけでは日本語(マルチバイト文字)の 単語移動ができない。現在では select-word-style スタイルを利用する方がよい。

最近ではカーソル移動目的にも インクリメンタルサーチを積極的に使うようになったので、 今となってはあまり重要ではなくなった。

aliasに引数を渡せない

多機能シェル最大の魅力をaliasに感じていた。cshでも山のように設定していた。zshにもあって安心したが、まず文法が違う。 cshだと以下のように定義できた。

alias ls ls -F

しかし、zshでは以下のようにクォートが必要で面倒に感じた。

alias ls='ls -F'

また、cshで以下のように設定していた。

alias dir 'ls -lF \!* | more'

しかし、これもaliasを定義しようとして挫折。定義の途中に引数を持たせられないのだ。アホちゃうか? ……と思ったがもちろんアホなのは自分で、そもそも不自由なaliasより、もっと素直に定義できるシェル関数を定義すればよいだけだ。zshのalias定義にクォートが必要なのも、以下のように効率的に書けることが 分かれば納得できる。

alias ls='ls -F' ll='ls -l' la='ls -a' lsr='ls -lR'

些細なことかもしれないが、zsh の内部コマンドは複数の引数をまとめて処理できるものはできるだけ1文で書けるような文法設計になっている。たとえば、変数をエクスポートする処理、つまりcshで言う setenv する処理は、zshの内部コマンド export でできるため、以下のようにまとめられる。

export NAME='HIROSE Yuuji' FOO=foo BAR=bar BAZ=bazzz QUX=qux QUUX=quux

キー割り当てを変える bindkey コマンドも同様である。これを知って設定ファイルを書くと、ファイルがコンパクトになり、見通しも立てやすい。

ちなみに setenv がないのも最初は不便に感じて、以下のシェル関数でcshの摸倣をしていた。

setenv () {export $1="$*[2,-1]"}

しかし、しばらくしてコマンドラインで setenv することは、⁠shを使っている限り)まずないと分かった。なぜなら、環境変数は特定のソフトウェアに情報を伝えるものなので、もし「常に⁠⁠ 変数の値を渡したいなら初期化ファイルでexportしておけばよいし、 ⁠一時的に」変数の値を変えて起動したいなら、以下のように起動すればよい。

% 変数=値 コマンド

いつもとは違う環境変数を設定して、しばらくの間、作業したいときは敢えてこうしたりする。

% LC_CTYPE=ja_JP.UTF-8 sh

「いつもとは違う作業中」であることが 明確に分かるようにするには、シェルを変えることも便利である。

zsh乗り換え直後の幸せ

zshを本格的に使い始めて間もなく、これはもう元に戻れないと強く実感した。とくに印象的だったのは以下の機能だ。

自動ヘルプとコマンドラインスタック

コマンドラインを入力中、オプションを再確認したくなる。 たとえば sudo コマンド。以下の文字列まで打ったところでオプションを確認したくなった。

% sudo -

zshでは ESC-h とタイプすると自動的に man sudo が実行される。読み終わってコマンドラインに戻ると入力途中の 同じカーソル位置に戻る。筆者の場合 sudo は頻繁に使うので alias s=sudo と、s だけで sudo している。そのような場合もzshはaliasを 展開したあとのコマンドを調べて man sudo してくれる。 aliasを開いて判断してくれるのはコマンドライン補完時も同じで こういう細かい挙動に抜かりがないのもzshのうれしい点だ。

マニュアルを読むことに限らず、コマンド打っている途中で 別のコマンドを打ちたくなったとき、zshでは ESC-q を 押すとコマンドラインがスタックに積まれ、空のコマンドラインとなる。新しいコマンドを実行し終えると、またスタックからコマンドラインが 下ろされる。tcshやbashではコマンドラインエディタのCUT&PASTEで1つのコマンドラインだけは保存しておけるが、スタックのように2段、3段…と多重に保管はできない。

ESC-hESC-q どちらも、思いつきでコマンドをどんどん入れたがる筆者には欠かせない機能だ。

強力なファイルグロッブ

tcshを使っていた頃はまだまだ経験値が足りず、ファイルを 選別するのに使う find コマンドが大の苦手だった。 なにかファイルを検索するのにその都度 find のマニュアルを 見ないと分からず……、いやマニュアルを見てもよく分からず適当に打っていた。弱い。

zshの **/ がディレクトリを再帰的に検索することでものすごく救われた。ついでに見付かったファイルの中から通常ファイルのみを選んだり、ディレクトリのみを選んだりするのもとても簡単だ。たとえば「カレントディレクトリ以下にあるすべての*.log または *.aux という通常ファイルのうち 4日以上アクセスしていないものは全部消す」としたい場合の find を使った例とzshの例を示す。

% find . '(' -name '*.log' -o -name '*.aux' ')' -a \
      -type f -a -atime +3 -exec rm '{}' ';'
% rm **/*.(log|aux)(.m+3)

ちなみに上の例の、find の例を作るのに、manを調べ、一応分かったけれども自信がないのでGoogle先生にお伺いを立て、実験してたぶん大丈夫とわかるまでに15分近くかかった。困ったことに上記の find で完全にあっている自信がない。さらに言えば、find の使い方を教わりにきた学生に所々あるクォート ' ' が何で必要なのかとか、何で丸い括弧ととんがり括弧があるのかとかを正確に理解させる根性ははっきり言って無い。

find の文法を覚えても、find でしか役に立たない。いや、tcpdump などにちょいと似ているから完ぺき無駄って訳じゃないが、それと格闘する時間はどうかデートに使ってくれと言いたい。

柔軟な補完

tcshの complete でたっぷり補完機能定義をしていたため、それとほぼ同等の機能は欲しかった。乗り換え当時のzshの補完機構 compctl は、最初難解に感じたが パターンさえ把握すればtcshと同等のものがすぐ作れると 分かった。それ以上に、補完候補を決めるときにシェル関数を 呼べることが分かって「これはもう何でもアリだ」と強く感じた。

関数が呼べるなら、どんなコマンドでも呼べる。 ということはUnixでできることは何でもできるということだ。

シェル関数

補完でシェル関数が使えると分かり、それと同時にcshには関数がないのに我ながらよく使っていたものだと感じた。Emacs-Lispで関数が使えなかったら誰が使うだろう、というか、 ウィンドウマネージャFVWM2の設定ファイル.fvwm2rcにだってユーザ定義関数を書けるというのに。

sh系のシェルがどれほど優位なのか思い知ったのがこの点だった。

『zshの本』

zshに完全に乗り換えた1994年当時、周囲には誰もzshを使っている人間はいなかった。だがtcsh全盛の時代で、シェルのカスタマイズに燃える人は多かった。tcshはすごい、でもzshはもっとすごい。これは何とか広めたい、と思ってまずはマニュアル(zshバージョン2.6のもの)を和訳し公開した。同時に compctl 補完の設定例もWebにさらしておいたりもした。

そのWebがきっかけとなり書籍化の話が出たのが2003年だった。引き受けたものの、当時既にzshの補完機能は compsys という、より洗練されたものに移行していて、これがさっぱり分かっていなかった。マニュアルの該当箇所を見ても、細かい関数の仕様説明が並んでいるだけで、どういう体系のものでどう考えて使えばよいのかの説明は見当たらなかった。難しすぎる。舟橋君『UNIX USER』に書いていたcompsysの解説記事を何度も何度も読んで、自分が使える程度の理解はできたものの、それを説明できるほどの知識には程遠かった。結局やはり地道にマニュアルの和訳から入ることにした。と決断したものの、学生時代に訳したものより長いうえに、当時ほどの時間もなく、読んで理解しても次に読むときには前のことを忘れてしまうありさま。全部訳すのは諦め、理解するのに最低限のところだけ訳すことにした日本語 zsycompsys.txt⁠。この割り切りでやっとcompsysの全体を理解でき、 zshの本の完成に辿り着けそうだと思えた。これが2007年。いやはや、大きな山だった。

zshユーザはおそらく濃厚な制覇欲がある。これを裏切らないよう、本書では、補完システム以外でも、自分がこれまでshで難しいと感じていた部分を、理解した順番で説明するようにした。全体の半分程度はzshではなくshの説明になっているので、本書でzshをマスターする頃には同時に shをも制覇してもらえるのではないかと考えている。

そういう理屈はさておき、zshは「こうなるといいな」と思ったことがほぼすべて実現できる柔軟性を持ったシェルであり、なにより使っていて楽しい。まずはインストールしてみて、使ってみてほしい。次回は、別のシェルから乗り換えるときにつまずきがちな点などを紹介しよう。

おすすめ記事

記事・ニュース一覧