ラインエディタ体感ツアー――vi/vimの「動作モード」なるものの謎に迫る

相変わらずvi/vimの操作方法は初めて使うユーザを惑わせるものだ。

よく「viには動作モードが2つある」的な説明がされる。「ノーマルモード」と「入力モード or 挿入モード」だ*1。この説明は昨今のユーザにも理解しやすいものだが、しかし「何でそんな『動作モード』なんてのがあるの? 不便だよ」という返事が返ってくるのがオチだろう。

viやvimがあんな操作方法なのはWikipediaのviの項でも微妙に触れられているように「ラインエディタ的な操作方法を継承しているから」が一番正解に近いのだが、そう聞いただけでは今時の若者どころかナウなヤング*2でさえイマイチ理解できなかったりする。ラインエディタを使ったことのある人が少ないからだ。

そこで人生の寄り道*3としてviの先祖であるラインエディタedを触り、原始の力*4に戦慄すると同時にvi/vimの操作方法について考察しようと思う。

え、Windows使いだからedが無い? ご安心を。Cygwinを入れるまでもなくGNU edのWindows版があるのだ! やったね、これで明日はホームランだ!

/bin/ed によるテキスト編集ツアー

Edは寡黙だ。寡黙すぎる。多分モテないのではないかと思う。寡黙さゆえの非モテ

$ cat > hello.c
#include <stdio.h>

int main(void)
{
        (void) puts("hello, world");
        return 0;
}
$ ed hello.c
80
_

ファイルを開いてもバイト数しか表示されない。黙って背中で語る、それがEdの生き様。まあしかしプロンプト表示が何もないのも味気ないので、オプション -p を使ってもよいだろう……GNU以外のedでも大丈夫だろうか?

edによるテキスト編集は、sftp(1)とかを対話的に使用するケースに近い。コマンドを入力すると、その内容に応じた処理が実行される。

例えば1行目から最終行まで表示したい場合 `1,$p' というコマンドを実行する。

$ ed -p ":" hello.c
80
:1,$p
#include <stdio.h>

int main(void)
{
        (void) puts("hello, world");
        return 0;
}
:_

`p' は指定した行の内容を表示するコマンドだ。`1,$' が範囲を指定している部分だ。`$' は最終行を意味する。sed等の正規表現では行末を意味する記号なので、何となくそこから連想できるのではないだろうか?

この文書に1行追加してみよう。viには `a' や `i' というコマンドがあるが、edにも全く同じコマンドがある。違いは1点だけ。viではカーソルの前後に入力した文字を挿入するが、edでは指定した行の前後に入力した行を挿入する。

:1a
#include <stdlib.h>
.
:1,$n
1       #include <stdio.h>
2       #include <stdlib.h>
3
4       int main(void)
5       {
6               (void) puts("hello, world");
7               return 0;
8       }
:_

`1a' で1行目の次に新しい行を追加している。`.' のみの行は追加行の終了を指示している。

更に編集を続けよう。折角stdlib.hをインクルードしたので、戻り値をマクロ定数にしてみる。

:7s/0/EXIT_SUCCESS/
:1,$p
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
        (void) puts("hello, world");
        return EXIT_SUCCESS;
}
:_

edによるテキスト編集は行単位で行う。ある行の一部分を書き換えたいなら、コマンド `c' でその行を丸ごと新しい行で置き換えるか、コマンド `s' で正規表現による置換を行うしかない。そこで7行目を `s' で置換してみた。vi/vimユーザなら馴染みがある書き方ではないだろうか?

一通り編集したので、別のファイルとして保存して終了しよう。

:w hello_ed.c
111
:q
$ _

`w' や `q' もvi/vimユーザには馴染み深いと思う。

ここまで見てきたように、edによるテキスト編集はコマンドによる対話操作の繰り返しだ。コマンドを実行すると、その内容に応じてテキストが編集される。編集が行単位で適用されることを除いても、現在一般的なテキストエディタとは操作感が随分異なる。

/bin/ed 恐怖症の方にささげる、exによる編集ツアー(ダイジェスト版)

世の中にはedを使いたくない人もいるかもしれない。

edには懲りた、もう沢山だ……そんなあなたに朗報。edを使わなくてもvi/vimでラインエディタを体感できる、その名もex!*5 何か強そうな名前だぞex!

$ ex hello.c
"hello.c" [converted] 7L, 80C
Entering Ex mode.  Type "visual" to go to Normal mode.
:1,$p
  1 #include <stdio.h>$
  2 $
  3 int main(void)$
  4 {$
  5 >---(void) puts("hello, world");$
  6 >---return 0;$
  7 }$
:1a
#include <stdlib.h>
.
:1,$p
  1 #include <stdio.h>$
  2 #include <stdlib.h>$
  3 $
  4 int main(void)$
  5 {$
  6 >---(void) puts("hello, world");$
  7 >---return 0;$
  8 }$
:7s/0/EXIT_SUCCESS/
  7 >---return EXIT_SUCCESS;$
:1,$p
  1 #include <stdio.h>$
  2 #include <stdlib.h>$
  3 $
  4 int main(void)$
  5 {$
  6 >---(void) puts("hello, world");$
  7 >---return EXIT_SUCCESS;$
  8 }$
:w hello_ex.c
"hello_ex.c" [New] 8L, 111C written
:q
E37: No write since last change (add ! to override)
:q!
$ _

今回触った範囲ではedもexも使い方はほぼ同じだ。ここではvimをexとして使用しているので、.vimrcの内容が適用されている。

考察

edもexも、その操作方法は、

  1. コマンドを入力し、実行する。
  2. 実行結果がテキストに反映される。

というサイクルを繰り返すものだ。

ここからvi/vimのあの独特の操作方法の理由を導くことができる。viもvimも、その操作は「コマンドを入力して実行 -> 結果が反映される」の繰り返しなのだ。

edやexとvi/vimの違いは、ラインエディタである前者が指定した行を基点として行単位で編集を行うのに対して、スクリーンエディタである後者は指定したカーソル位置を基点として文字単位で編集を行えるように拡張されている所だ。

`h'、`j'、`k'、`l' といったviのカーソル移動コマンドは、コマンド操作を適用する基点を変更しているに過ぎない。`a' や `i' コマンドで編集処理を実行すると、そのカーソル位置に対してコマンドが実行され、その結果がテキストに反映されると同時にスクリーンにリアルタイムに表示される。

テキストの内容がフルスクリーンで表示され、なお且つ編集結果(時には編集の過程も)がリアルタイムに表示される(ことが多い)為に我々は誤解してしまうのだが、vi/vimの操作方法はedやexといったラインエディタ時代のものを色濃く受け継いでいる。他のエディタがラインエディタとは全く異なる操作方法を採用しているのとは正反対だ。

最初からラインエディタとは異なる操作方法のエディタを使ってきて慣れている為に、ラインエディタの操作方法を色濃く残しているviやvimの操作で戸惑う――それがviやvimの操作で戸惑う理由の一端だろう。

*1:vimの場合は「この2つ+αの動作モードがある」的な説明になる。

*2:元「今時の若者」のこと。

*3:今では普通のプログラマならラインエディタに触れることなく一生を終えることができると思う。というか普通のユーザならテキストエディタに触れる機会すらないかもしれない。

*4:『ホゲゆに』より。

*5:`ex' で駄目な場合は `vim -e' とか。