vi
Last Modified: Tue Dec 27 00:06:02 JST 2011

目次

  1. イントロダクション
  2. 基礎
  3. 組み合わせ
  4. 編集コマンド
  5. カウント
  6. カーソル移動コマンド
  7. スクロールと再表示
  8. 入力モード
  9. 置換
  10. アドレス指定
  11. 正規表現
  12. ex コマンド
  13. set コマンド
  14. マクロ定義
  15. 起動時オプション指定
  16. その他役に立たない情報

イントロダクション

いまどき vi を使う理由って、いったいなんだろう。 はっきりいって vi は、簡単には使いこなせない奇妙なエディタだし、 機能だってそれほど多くない。 漢字入力はそもそも不可能で、漢字対応バージョンでも非常に使いづらくてやっていられない。

一般的なエディタは、キーを押すとその文字がそのまま入力される。 カーソル移動やファイルのセーブなどのコマンドは、機能キーやコントロールキーとの組み合わせに割り当てられていて、文字の入力とコマンドとをダイナミックに打ち分けることができるようになっている。 Emacs や、FreeBSD 標準の簡易エディタ ee などがその典型例だ。

ところが vi は、コマンドモードと入力モードがはっきりと分かれている奇妙なエディタだ。 文字を入力するには入力モードへ移行するコマンドを入れなければならないし、 コマンドを実行するにはいちいち入力モードから抜けなければならない。 コマンドモードで使えるコマンドには普通の文字が割り当てられているので、うっかりコマンドモードで文章を入力すると、それがコマンドと解釈されて不愉快な思いをするということもしばしばだ(慣れればそんなことは起こらないが)。

こんな使いにくいモード指向に、どんな利点があるのだろうか? 実はそれなりに利点があり、それこそが vi の特長なのである。 vi の上手な使い方を、順を追って説明していくことにしよう。

基礎

まず最初に、最低限必要と思われる12個のコマンドを示す。 この12個さえ覚えれば、どんなファイルでも編集できるはずだ。

もしあなたが vi 初心者であるならば、これ以上先を読む前に12個のコマンドだけで vi に慣れておくことをお勧めする。 この先楽しいコマンドが次々に出てくるのだが、 この12個を完全にマスターするまで続きを我慢したほうがよい。

組み合わせ

次に示すのは、コマンドモードでカーソルを移動させるためのコマンドだ。 カーソル移動といえば、普通は矢印キーによる上下左右のことだ、と思うかもしれない。 だが vi には、実に30個以上の「カーソル移動コマンド」がある。 その中から、最初の5個を紹介しよう。

これらカーソル移動コマンドは、せいぜい hjkl を連打するのを防ぐぐらいの意味しかなさそうに見える。 確かにこれらのコマンド単体では、ちょっと楽をするぐらいしかできない。 だがここで、編集コマンドと組み合せてみると、大変な威力を発揮するのだ。

基礎で1行を削除する dd コマンドを紹介したが、 このコマンド d と、上記のカーソル移動コマンドを組み合わせてみよう。 たとえば次のような場合を考える。

If you love somebody, set them free.

カーソルが l の上にあったとしよう。 ここで「dw」と入力してみよう。 するとコマンド実行後はこうなる。

If you somebody, set them free.

単語 love が削除されて、カーソルは次の単語(だった)somebody に乗っているというわけである。 これは削除コマンド d と次の単語への移動を意味する w との組合わせの結果なのだ。

他のコマンドとも組み合わせて見よう。 次は d$ を試してみよう。 コマンド $ は行末へのカーソル移動だから、d$ は行末までを削除という意味になる。

If you love somebody, set them free.
       ↓ d$
If you 

d との組合わせは、行末方向へ限られているわけではない。 0 と組み合わせて、行頭までを削除することもできる。

If you love somebody, set them free.
       ↓ d0
love somebody, set them free.

f コマンドとの組み合わせも試そう。 f, と入力すれば、カーソルは , の位置へ移動する。 d と組み合わせれば、, までを削除という意味になる。

If you love somebody, set them free.
       ↓ df,
If you  set them free.

t コマンドなら、文字の手前までを削除ということも可能だ。 一見奇妙な t コマンドだが、実際に使ってみると大変便利だということが分かると思う。

If you love somebody, set them free.
       ↓ dt,
If you , set them free.

このように、d コマンドはどんなカーソル移動コマンドとも(きちんと意味さえあれば)組み合わせることができる。 今覚えた5個のコマンドが、用法によって倍の意味を持つことになったわけだ。

編集コマンド

最初に dd として紹介した d コマンドだが、実際には「…を削除する」という意味を持つことを説明した。 このようにカーソル移動コマンドと組み合わせる編集系のコマンドを紹介しておこう。 c と y だ。

c コマンドは、d と i を組み合わせたようなコマンドだ。 たとえば cw は、 dwi と入力するのと同じ意味だ。 だが cw の方が優れている理由が2つある。 ひとつは cw の方が1文字少なくてすむこと、もうひとつは変更する範囲を $ マークで示してくれることだ。 c コマンドはこれまでに紹介したすべてのカーソル移動コマンドと組合わせが可能だし、cc と入力すれば(dd と同じく)行に対して作用する。 つまり現在の行を丸ごと削除し、入力モードへ移行する。

y は指定範囲をコピーするコマンドだ。 ただコピーするだけなので、画面上では何の変化も起こらない。 コピーした文字列を呼び出す、別のコマンドを覚えなければならない。

ほとんど同じ意味のコマンドが2つある理由は、p だけでは行頭へペーストできず、 P だけでは行末にペーストできないからだ。

さて、ここまでで合計21個のコマンドを紹介した。 このうち接頭子となるコマンドが d、c、y の3個、カーソル移動コマンドが9個あった (hjklもお忘れなく! 組み合わせるとどうなるかは試してみよう)。 これらの組合わせから、実に 30 通りのコマンドが生み出せる。 つまり、現時点で 51種類の操作が可能になったというわけだ。

カウント

続いて紹介するのがカウントだ。 カーソル移動にしても削除にしても、10文字とか20行といったまとめた範囲を一度に対象にしたいことがある。 vi コマンドの多くは、このような場合に便利なカウント指定が可能となっている。 カウントはコマンドの前に数字で指定する。 w コマンドを例にとって説明しよう。

vi is a screen oriented editor.
      ↓ 3w
vi is a screen oriented editor.

3w と入力すると 3つ先の単語へカーソルが移動したのが分かる。 これは www と入力したのと同じ効果だ。 f コマンドも同様にカウントを指定できる。

vi is a screen oriented editor.
      ↓ 4fe
vi is a screen oriented editor.

4fe という入力で、4つ先の e にカーソルが移動する。 これは fefefefe と入力するのと同じだが、4fe の方が断然短い。

カウントは、d コマンドでも使える。 「4つ目の e までを削除する」という意味で d4fe と指定してもよいし、 「e までを削除、を4回繰り返す」という意味で 4dfe と指定してもよい。 効果は同じだ。

vi is a screen oriented editor.
      ↓ d4fe
vi is d editor.

dd コマンドでカウントを指定すれば、行をまとめて削除することになる。 4dd とやれば4行削除だし、100dd とやれば100行削除だ。 行数を目で数えたりしなくても、正確に指定した数だけコマンドを実行できる。

このようなカウントは、すべてのコマンドで利用できるわけではない。 0 や $ のように、行に1つしかない位置へ移動するコマンドでは、カウントを指定する意味がない。 また、ZZ のようなセーブコマンドにもカウントは無効だ。

カーソル移動コマンド

カーソル移動系コマンドはここまでで9個を紹介した。 他に20個ぐらいあるのだが、全部を紹介したところで「普段使う機会あるのかな?」というコマンドも多い。 そこで覚えておくと便利なものだけを選んで紹介する。

ついでに、マークを付けるコマンドとジャンプするコマンドも紹介しておこう。 vi では、アルファベット a〜z を名前とするラベルを利用でき、任意の位置にマークとしてラベルを定義できる。 m コマンドがマーク、'(クォート) と ` (バッククォート)がジャンプだ。

m コマンドはマークを付けるだけだが、' ならびに ` はカーソル移動コマンドなので、d、c、y と組み合わせ可能だ。

スクロールと再表示

組み合わせ可能なコマンドの紹介で忙しかったので、ごく基本的なスクロール系のコマンドを忘れていた。 これらはあまり感動がないので、単にリストアップしておくだけにとどめておく。

z コマンドも大変奇妙なコマンドだが、案外便利なので笑える。

入力モード

コマンドモードに力が入りすぎて、入力モードがお座なりになってしまった。 入力モードへの移行コマンドも、実はたくさんある。 i と a だけで多くの場合はこなせるが、他にもよくありがちなシチュエーションで便利なコマンドが用意されている。

さらに、入力モードでのみ使えるコマンドもある。

置換

置換機能はエディタの基本機能だが、vi にも当然のことながら置換機能が備わっている。 ただ普通のエディタの置換コマンドとは異質のコマンド体系になっている。

普通のエディタならば、変換対象となる文字列と置換後の文字列を入力するために、ダイアログを表示するとかプロンプトを出すなどといった処理が行われる。 しかし vi では、ex モードからコマンド入力という方法で置換を行う。

vi には、コマンドモード、入力モードの他に ex モードというのがある。 ex モードとは、vi の前身であるラインエディタ ed の拡張版のことだ。 ex モードでは置換以外にもさまざまなコマンドが使えるが、どれも独特の書式を持っている。 まず最初に置換コマンドを示して、ex コマンドの使い方を調べていくことにしよう。

ex コマンドを使うには ex モードに移行する必要があるが、さすがの vi も ex モードへの移行と復帰は面倒だと思ったのか、「ex コマンドを1回だけ実行してすぐ vi モードへ戻る」というコマンドを用意している。 それが : (コロン)だ。 一応、vi モードから ex モードへ移行するコマンド Q というのがあって、これなら ex モードへ行きっぱなしになる。 ここから vi モードへ復帰する ex コマンドは「vi」だ。 これを知らないと、間違って Q を押してしまったきり vi モードに戻れなくなるという恐ろしい結末が待っているので注意しよう。

さて、ex モードでの置換コマンドは s である。 s は置換(substitution)のことで、ある文字列を別の文字列へ変換することを意味する。 次のような行があるとき(カーソルの位置は関係ない)、:s/bottle/letter/ と入力してみよう。

I hope that someone gets my message in a bottle.
                ↓ :s/bottle/letter/
I hope that someone gets my message in a letter.

ご覧の通り、bottle が letter に置き換わった。 s コマンドは、s/元文字列/新文字列/ という書式であることが分かる。 もっと短く :s/e/X/ と入力してみよう。

I hope that someone gets my message in a bottle.
                ↓ :s/e/X/
I hopX that someone gets my message in a bottle.

奇妙なことに、最初の e だけが X に変わった。 他にも e はたくさんあるのに、置換されるのは最初の1文字だけなのである。 すべての e を X に変えたいならば、末尾に g を付けてやる。 これは global の略だ。

I hope that someone gets my message in a bottle.
                ↓ :s/e/X/g
I hopX that somXonX gXts my mXssagX in a bottlX.

さて、g を付けても変換されるのは同じ行に限ってのことである。 これは ex コマンドが現在の注目行(カーソルの乗っている行)に対して実行されることに起因している。 それ以外の行にも作用させたいならば、s コマンドと同時に範囲(アドレス)を指定してやればよい。 もっとも単純なアドレスは行番号だ。 たとえば1行目から10行までを指定するなら、次のようになる。

:1,10s/bottle/letter/g

ファイルの最後の行の行番号は、$ で代用できる。 だからファイル全体を指定するなら 1,$ ということになる。 しかしこのようなファイル全体を指定するケースはよくあるので、もっと短い短縮形 % が用意してある。

:1,$s/bottle/letter/g
      または
:%s/bottle/letter/g

また、vi モードで付けたマークもアドレス指定で使える。 たとえば a と b というマークを付けておき、次のように指定すれば、マークからマークの間だけで置換を行うことができる。

:'a,'bs/bottle/letter/g

' というのは、vi コマンドのマークへのジャンプと同じだから、覚える手間も省けるはずだ。

さらに現在の行を示す記号もある。 . ピリオドがそれだ。 現在行から最後の行までで置換したいならこうなる。

:.,$s/bottle/letter/g

ex モードでのアドレス指定では、文字列検索も指定できる。 vi コマンド / と ? を思い出してほしい。

:?<table>?,/<\/table>/,/s/bottle/letter/g

これは、現在行の前後にある「<table>」から「</table>」までの範囲(行単位)の中で置換を行う (/ にはパターンを区切る特殊な意味があるので、\ を付けてエスケープしなければならない)。 たとえばこんなシチュエーションだ。

bottle!

<table>
 <tr>
  <td> bottle? </td>
  <td> message!? </td>
  <td> letter!! </td>
 </tr>
</table>

bottle...

このケースでは「bottle?」だけが置換対象となる。

アドレス指定

置換コマンドのアドレス指定について、もう少し詳しく説明しよう。

アドレス指定は基本的に「開始アドレス,終了アドレス」という形をしている。 この後に続けてコマンド(これまでは s コマンドを見てきた)を指定すると、アドレスで指定された各行でコマンドが実行されるわけである。 アドレスには、次のようなパターンを指定できる。

さらに、広域指定を用いることで、細かな条件で絞り込が可能だ。 広域指定は g/パターン/ という形式をしている。 これはファイルからパターンに一致する行をすべて選択し、それぞれの行でコマンドを実行するという意味になる。 たとえば g/alpha/s/STR/str/g というコマンドは g/alpha/ という広域指定と s/STR/str/g との組み合せだ。 意味は「alpha という文字列を含むすべての行で、STR を str に置換する」となる。

広域指定とアドレス指定は組み合せ可能だ。 アドレス指定に続けて広域指定を行えばよい。 上記の置換を、20行目から50行目の間に限定したいなら 20,50g/alpha/s/STR/str/g となる。

広域指定 g は「パターンに一致した行」という意味だったが、逆に「パターンに一致しない行」を指定したいこともある。 そのときは v が使える。 v/beta/s/STR/str/g は、「beta という文字列を含まないすべての行で、STR を str に置換する」という意味になる。

正規表現

vi では検索パターンに正規表現が使用できる。 これは ex モードの s コマンドに限らず、/ や ? のような文字列検索コマンドでも使うことができる。

正規表現について説明を始めると長くなってしまうし、Unix ではあちこちで正規表現を使う機会があるはずなので、参考書でも読んで覚えるようにしてほしい。 一応、簡単な説明だけ載せておく。

他に置換コマンドの検索パターンで使える特殊な記号もある。 \( と \) だ。 このカッコでくくったパターンは、マッチした文字列を一時的なバッファに蓄える。 蓄えられた文字列は、置換文字列内に呼び出すことができる。 1番目のカッコは \1、2番目のカッコは \2 のように指定できる。

ちょっと例を見てみよう。 たとえば次のように、姓名の順で書かれた行があったとする。 区切りはスペースだ。

sasada ryo
hayashi tamon
washikita ken

これを Firstname Lastname の順に並べ替えたいときはこのようなコマンドになる。

:%s/\(.*\) \(.*\)/\2 \1/

これで、元の名前が何百行あろうといっぺんに置換が可能だ。

ex コマンド

ex コマンドには、s 以外にも様々なものがある。 多くはファイルを操作するためのコマンド、すなわちセーブしたりロードしたりするためのコマンドだ。 これもよく使うものだけ抜粋して紹介しよう。

w や e や q のようなコマンドは、ファイルを変更したり現在の編集バッファを捨ててしまったりといった効果がある。 このようなとき、vi はファイルの属性やバッファの変更フラグをチェックして、禁止されていたりすると「できません」と文句を言う。 そういうときは強制的に実行するよう ! を指定してやる。 最初に :q! を紹介したが、これは編集バッファに変更があってもそれを捨てるということを意味する。

set コマンド

set コマンドは vi の様々なオプションを設定するための ex コマンドだ。 vi では実に様々な機能があるが、その動作を設定したり変更したりするのが set コマンドの役割だ。 set で指定できるオプションの一覧は、:set all で見ることができるので試しにやってみてほしい。 ここでは代表的なものだけを紹介する。

インデントを有効にするには :set autoindent と入力する。 短縮形の :set ai でもよい。 無効にするには :set noai のように、no を付けて指定する。 ちなみに vi のデフォルトでは、noai、nonu、noro、ws となっている。

マクロ定義

あまり知られていないが、vi ではキーボードマクロやコマンド定義もできる。 :map コマンドを使ったコマンド定義について、簡単に紹介しておこう。

less を使っている人なら知っていると思うが、g というコマンドでファイルの先頭に簡単にジャンプできる機能がある。 これは便利なのだが、vi では存在せず、1G と入力するしか方法がない。 幸い vi では g コマンドは空いているので、これを less と同じコマンドに割り当ててしまおう。

:map g 1G

:map は、文字に対して文字列をマッピングし、コマンドモードでそれを押すとマッピングした文字列の方を実行する。 上記の例では、g を押すと 1G と入力されたのと同じ効果が現れる。

:map では、ex コマンドも指定可能だ。 ex コマンドでは改行まで含めて指定しなければならないが、^V を使って改行自身を入力可能なので問題はない。 たとえば2つのファイルをオープンしているとき、前のファイルへすばやく切り替える q というコマンドを定義してみよう。 ちなみに q も空いているので定義してしまっても問題ない。

:map q :e#^M

# というのは、前に編集していたファイル名を表わす特殊文字だ。 :e コマンドと組み合せることで、直前のファイルへの切り替えを指定することができる。 ^M は、^V を押した後改行キーを押せば入力できる。 「^」と大文字の M を入力しないように注意してほしい。 vi はバッファを捨てるときに変更チェックをするので、編集しながらだと使いにくいのだが、参照するだけなら割に便利に使うことができる。

起動時オプション指定

:set や :map のようなコマンドは、vi を終了すれば元の設定に戻ってしまう。 しかし起動のたびにマクロを定義したりするのは面倒だ。 そこで、起動時に設定を自動的に行うようにしよう。 これには2通りのやり方があるが、環境変数を使った方法だけを紹介しておく。 もう一つのやり方は .exrc ファイルを作ることだ。 興味がある向きは man ページを見てもらいたい。

vi は起動時に EXINIT という環境変数をチェックする。 この環境変数に ex コマンドを設定しておくと、vi は起動時にそれを実行してくれるのだ。 EXINIT の初期化を .cshrc にでも書いておけば、vi の動作をカスタマイズできるというわけである。

たとえばオートインデントをオンにし、先ほど紹介した g と q マクロを設定する場合、次のようになる。

setenv EXINIT 'set ai|map g 1G|map q :e#^M'

| はコマンドを区切るために使えるので、いくらでも ex コマンドを並べることができる。

その他役に立たない情報

vi を深く理解するためには、ed を学んでおく必要がある。 ed は Unix の Version 1 からある由緒正しいエディタだが、 ラインエディタという今となっては時代遅れも甚だしい古いプログラムでもある。

現在、高密度グラフィックは当たり前になっている。 そもそも高密度グラフィックという言葉自体が意味を成さないほどだ。 しかし ed 当時のコンピュータでは、文字を表示するだけの端末がほとんどで、 現在の基準からいえば最低レベルのグラフィック機能さえ高価すぎて一般では使えないような状況だった。 CRT ディスプレイがあればまだ良い方で、 プリンタにキーボードがくっついたようなテレタイプという機械しかない場合も多かった (現代でも Unix では端末のことを tty と呼ぶが、これは teletype の省略形なのである)。 テレタイプでは、打った文字は紙に印字されてしまうから、修正など不可能である。 そもそも画面という概念もなく、行に対してコマンドを使い、 いちいちプリントしなおして確認していく作業となる。 かなり面倒だが、リアルタイムで画面を書き換えられない端末では他の方法がなかったのである。 いやはや恐ろしい時代もあったものだ。

技術が進んで CRT の値段も下がり、端末として普及してくると、 画面の上で(紙を無駄にせず)編集できるようなエディタが開発され始めた。 しかし CRT 端末は新たな問題も抱えていた。 カーソル移動や画面消去、スクロールや文字の挿入といった特殊機能が、 端末のメーカーや機種によってバラバラだったのである。 スクリーンエディタは多くの場合、特定のメーカーや機種に限定して作られ、 どんな端末でも使える汎用のものはなかった。 仮に汎用のものを作ろうとしても、個別の端末ごとに対応を迫られ、 作成には非常に手間がかかった。 何十、何百とある端末のすべてで使える便利な方法が必要だ…というわけで、 Unix で作られたのが termcap と curses ライブラリだった。

termcap とは terminal capability の略で、 端末上で使えるコマンドをデータベース化したものだ。 termcap は 「カーソルを右に動かす」 「行末までを削除する」 「文字を1文字挿入し、残りの文字を右にずらす」 といった機能を分類し定義するリストを持っている。 端末を作成したメーカーや、その端末を使いたいユーザは、 定義されたコマンドに対応する端末制御コード(エスケープシーケンス)を termcap に記入する。 あとは画面制御を行いたいプログラムが、 端末名を頼りに termcap から適切な制御コードを検索するわけである。 curses は termcap を使ってコマンドを実行してくれる画面制御用ライブラリで、 プログラムの作成が格段に楽になった。 curses によって画面制御を行うプログラムは多数作られた。 less(逆スクロール可能な、高機能版 more)などが典型的な例だ。 特にゲームは curses をフルに活用している。 冗談で、curses を使った最初のアプリケーションは rogue だと言われるほどである。

vi もまた、curses を使って作られた。 termcap さえ用意すればどんな端末でも動作する vi は、 すぐに普及して多くのユーザに使われるようになった。 vi は ed の拡張版ラインエディタ ex をベースに作られているので、 ed の時代から使っている人も、ed を知らないスクリーン志向の人も両方に使われた。 ed に代わって Unix 標準エディタの座を確立するのに、 それほど時間はかからなかったと思われる。

vi の独特のコマンド体系は、ベースとなった ed の影響が強い。 強いモード志向は、まさにラインエディタそのものだ。 キー割当も非常に独特だが、やはり ed コマンドを由来とした形態になっている。 カーソル移動キーが hjkl に割り当てられているのは、 rogue や他の多くのゲームと同様、 vi が curses を使った正統なアプリであることの現われだ(ほんとかな?)。 vi の入力モードではカーソル移動にはとてつもない制限がかかるが、 これはラインエディタの制限と同じなのである。

なぜカーソル移動が hjkl なのか? Bill Joy が使っていた ADM-3a という CRT ターミナルのキーボードには、 hjkl のキーにカーソルの矢印が刻印されていたのだ。 全景キーの拡大図

以上はオリジナルの vi の話だ。 現在 FreeBSD に標準で付いてくる vi は、nvi というクローンである。 nvi は大変出来のよいエディタだが、オリジナルと微妙なところで動作が違っていたり、新機能が追加されていたりする。 たとえば入力モードでカーソル移動ができ、しかも行を超えて移動できるという卒倒ものの拡張がなされている。 またオリジナルの vi では「直接の編集対象はバッファである」という信念に基づきファイル名を指定しないと名無しのバッファが編集対象となっていたが、nvi では tmp ファイルだという点も気になる。

vi には他にも様々なクローンがある。 が、はっきりいってお勧めできるものはあまりない。 たとえば Linux では vim が普通だが、c コマンドを使ってみると、変更範囲を $ を使って示してくれず、単に削除してから入力モードへ落ちるようにできている。 実はオリジナルのvi が変更範囲を $ を使って示すのは、視覚的な効果を狙っているのではなく、「あまり画面をたくさん書き換えると遅い端末の人がかわいそうなので、なるべく文字削除とかはしない」という curses の設計方針のためなのである。 curses を使っていると、 他にも「端末スピードを稼ぐため」の機能があちこちに見えるのだが、 現代のように速い端末が当たり前になって、 そういう努力は目に見えないというわけだ。

--- と書いてからずいぶん時間がたった。 最近(2004年2月)のバージョンを確認してみたところ、 以前気に入らなかったクローンの微妙な動作は、多くが解消されていることが判った。 vim については、変更範囲を示す $ も実装され(面倒だったろうなあ)、 ちょっと見た目はオリジナルと区別できなくなっている。 elvis も、無限に長い右マージンを off にできるようになっていた。 入力モードからエスケープで抜けたときカーソルが1文字バックする、 というバクと勘違いしそうな実装も気になるが、 最新版ではすべて問題なくなっていた (10年ぐらい前はちゃんと動かなかったように思う)。 今となっては、vim や elvis をお勧めしない理由はほとんどない。


[back to index]