サーバーサイドのエンジニアの皆さんを始めとして、多くの方に利用されているvi
エディタ。
しかしながら、i
とESC
と矢印キーしか押さずviを単なるテキストエディタとして利用している方が多いように感じます。
ちがう。viはそんなんじゃない。矢印キーには画鋲でも貼り付けておけ!!
というわけで、viの基本的な操作方法を示します。これでエンジニアの生産性が少しでも上がったら幸いです。以下を読んで、へぇ〜って思ったいただけた方は更にもう一歩すすんで、実際にvi
を起動して操作してみてください。「知る」だけじゃなく「身に付ける」ことが重要です!
移動操作
viの基本操作としては「移動」と「編集」を理解すると生産性が抜群に上がります。
ここではまず「移動」に関する操作を示します。以下はviのEXモード(ESC
を押して編集モードから抜けた状態)で利用する操作になります。
h
j
k
l
矢印キーの代替キーです。
-
h
: 左へ移動 -
j
: 下へ移動 -
k
: 上へ移動 -
l
: 右へ移動
キーボードの右手ホームポジションの周辺にありますので、いちいち移動のために矢印キーまで手をずらすことなく操作することができます。
このキーバインドはvi以外でも結構使われていたりします。例えば、Gmailではキーボードショートカットを有効にした場合にスレッド間をj
,k
で移動することができます。慣れておくと意外な場面で恩恵に預かれるかも。
w
b
e
単語単位の移動です。
ハイフンやカンマ、句読点などを単語の区切りとみなして移動してくれます。
-
w
: 次の単語の先頭へ移動 -
b
: 前の単語の先頭へ移動 -
e
: 単語のお尻へ移動
# ↓カーソルがここにあるときに
self.service_enable?('web_host') || self.service_enable?('web')
# ↑ ↑ ↑`w`を押したときはここ
# ↑ `e`を押したときはここ
# `b`を押したときはここ
W
B
E
単語単位の移動です。
小文字の同コマンドと違い、ハイフンやカンマ、句読点などを単語の区切りとみなしてくれません。空白を単語の区切りとみなして移動します。
なので、小文字のコマンドを入力していてちまちまとしか移動できないときなどは、思い切って大文字入力して大胆に移動しましょう。
-
W
: 次の単語の先頭へ移動 -
B
: 前の単語の先頭へ移動 -
E
: 単語のお尻へ移動
# ↓カーソルがここにあるときに
self.service_enable?('web_host') || self.service_enable?('web')
# ↑ ↑ ↑`W`を押したときはここ
# ↑`B`を押したときはここ ↑`E`を押したときはここ
0
$
行の先頭/末尾への移動です。
-
0
: カーソルがある行の先頭へ移動 -
$
: カーソルがある行の末尾へ移動
ちなみに、0
は数値の始まりを表すから行頭を意味していると思われます。$
はなんだろう? 正規表現とかでも行末を表すのに$
使いますよね。それと関連付けたら覚えやすいかも。
/文字列
?文字列
n
N
文字列検索です。これも移動の一種です。
/
で指定した文字列を検索した場合: カーソルより後ろにある文字列を検索
-
n
: カーソルより後の検索該当文字列の先頭へ移動 -
N
: カーソルより前の検索該当文字列の先頭へ移動
?
で指定した文字列を検索した場合: カーソルより前にある文字列を検索
-
n
: カーソルより前の検索該当文字列の先頭へ移動 -
N
: カーソルより後の検索該当文字列の先頭へ移動
このキーバインドはless
やlv
コマンドでも利用可能ですね。
m文字
`文字
'文字
m
はマーク機能です。文章中に見えないマークを付けてあとからその位置を呼び出して移動することができます。
m
の後ろの文字
は大文字小文字アルファベットの52種類を指定することができます。(そんなにいっぱい管理しきれませんが..)
-
`文字
は設定したマーク位置まで移動 -
'文字
はマークした行の先頭への移動
# ↓この位置で「ma」を押す
# Returns the plural form of the word in the string.
#
# "post".pluralize # => "posts"
# "octopus".pluralize # => "octopi"
# "sheep".pluralize # => "sheep"
# "words".pluralize # => "words"
# "the blue mailman".pluralize # => "the blue mailmen"
# "CamelOctopus".pluralize # => "CamelOctopi"
def pluralize
Inflector.pluralize(self)
end
# ↑カーソルがこの位置にあるときに 「`a」を押すとさっきのマークした位置までジャンプ!
# 「'a」を押すとマーク行の先頭までジャンプ!!
(バッククォートとシングルクォートがそれぞれどっちの意味だったかいつも分からなくなります.. どなたか良い覚え方/思い出し方を教えてください。)
数値+移動操作
上記で示した移動操作は数値と組み合わせることで同じ操作を複数回入力したのと同じ効果を得られます。
# ↓ここにカーソルがあるときに
select device_id from iphone_devices where customer_id=1;
# ↑`7l`を押すとここへ移動
# Returns the plural form of the word in the string.
#
# "post".pluralize # => "posts"
# "octopus".pluralize # => "octopi"
# "sheep".pluralize # => "sheep"
# "words".pluralize # => "words"
# "the blue mailman".pluralize # => "the blue mailmen"
# "CamelOctopus".pluralize # => "CamelOctopi"
def pluralize
Inflector.pluralize(self)
end
# ↑カーソルがこの位置にあるときに `10k`を押すと「# Returns 〜」の行まで移動
※2つ目の例はいちいち何行分か数えるくらいならマークを使ったほうが楽です。
編集操作
viの基本操作「移動」「編集」の後編、「編集」についてです。
i
Insertのi
ですね。カーソルがある位置に文字を入力するモードに遷移します。
a
A
Appendのa
ですね。カーソルがある位置の後ろに文字を入力するモードに遷移します。
-
a
: カーソルのある位置の1つ後ろから入力するモードに遷移する -
A
: カーソルのある行の末尾から入力するモードに遷移する
o
O
行を挿入する操作です。(Open blank lineのo
らしいですがピンときませんね)
たまに、i
を押して行末まで矢印キーで移動してエンターを入力しているのを見かけますが、o
で一発でできちゃいます。
-
o
: カーソルのある行の次に行を追加して入力モードに遷移する -
O
: カーソルのある行の前に行を追加して入力モードに遷移する
c
cc
Changeのc
です。
-
c
: 指定された移動分の文字列を削除した後に入力モードに遷移する (詳細は後述) -
cc
: カーソルのある行を丸々削除した後に入力モードに遷移する
r
Replaceのr
です。カーソルがある位置の文字を、r
に続けて入力した1文字で置き換えます。
この操作は入力モードには遷移せず、EXモードのままとなります。
s
Substituteのs
です。カーソルがある位置の文字を削除して入力モードに遷移します。
r
は置き換える文字が1文字でしたが、s
は任意の文字列で置き換えることができる点と、入力モードに遷移する点が異なります。
3s
のように数値と組み合わせると削除する文字数を変更できます。
x
カーソルがある位置の文字を削除します。削除した後は入力モードに遷移せず、EXモードのままとなります。
3x
のように数値と組み合わせて任意の文字数を削除することも可能です。
d
dd
Deleteのd
です。
-
d
: 指定された移動分の文字列を削除する (詳細は後述) -
dd
: カーソルのある行を丸々削除する
3dd
のように数値と組み合わせて(ry
y
yy
Yankのy
です。コピペの『コピ』です。
-
y
: 指定された移動分の文字列をクリップボードにコピーする (詳細は後述) -
yy
: カーソルのある行を丸々クリップボードにコピーする
3yy
の(ry
p
P
Pasteのp
です。コピペの『ペ』です。
Yankして明示的にクリップボードにコピーしていた文字列は当然対象ですが、Deleteした文字列などもクリップボードに入っており、それらのうちいずれか最新のものをペーストします。
-
p
: カーソルのある位置の後にペーストする -
P
: カーソルのある位置の前にペーストする
編集操作 + 移動操作
ここからが真骨頂です。編集操作と移動操作を組み合わせて実行します。
c
+ 移動操作
Change操作の対象を移動操作で指定します。以下は一例です。
-
cw
: カーソルの位置から次の単語の先頭までを削除して入力するモードへ遷移する -
c3w
: カーソルの位置から3つ先の単語の先頭までを削除して入力するモードへ遷移する -
c$
: カーソルの位置から行末までを削除して入力するモードへ遷移する -
c/文字列
: カーソルの位置から、指定した文字列までの間を削除して入力モードへ遷移する
d
+ 移動操作
Delete操作の対象を移動操作で指定します。以下は一例です。
-
dw
: カーソルの位置から次の単語の先頭までを削除する -
d'文字
: カーソルの位置からマークしておいた行までを削除する
例えば、関数定義をまるっと移動させたいときに、「関数が1,2,3,...,10行あるからd10j
だね!」なんていうことはせず、ma
とd'a
でサクッとやっちゃいましょう。
y
+ 移動操作
Yank操作の対象を移動操作で指定します。以下は一例です。
-
yw
: カーソルの位置から次の単語の先頭までをクリップボードにコピーする -
y'a
: カーソルの位置からマークしておいた行までをクリップボードにコピーする
その他
.
直前の編集操作の繰り返しです。
x
やr
などのEXモードから直接の編集操作や、i
やs
、cw
などの入力モードに遷移してからESC
でEXモードに戻ってくるまでの編集操作を再現してくれます。
例えば、下記のように「末尾にカンマつけ忘れた!!」といううっかりさんは、
-
A,
を入力して末尾にカンマを挿入 -
ESC
を押して入力モードを一旦終了 - あとは
j.j.j.j.
と入力
とすることで「末尾にカンマを挿入」という編集操作を繰り返し実行することができます。
CSV_COLUMNS = [ # ↓このへんにカーソルがあるときに`A,ESC`で末尾にカンマを挿入
app.users.tag_import_columns.number
app.users.tag_import_columns.target_column # `j.`で下に移動しつつ繰り返し操作
app.users.tag_import_columns.target_value # `j.`で下に移動しつつ繰り返し操作
app.users.tag_import_columns.tag # `j.`で下に移動しつつ繰り返し操作
app.users.tag_import_columns.memo # `j.`で下に移動しつつ繰り返し操作
]
「あー、そういえばusersじゃなくてdevicesだった..」といううっかりさんは、
-
/users
を入力して最初のusersを検索 -
cw
で単語削除しつつ入力モードに遷移し、devicesと入力して正しい文字列に編集 -
ESC
を押して入力モードを一旦終了 - あとは
n.n.n.n.
と入力
とすることで「usersをdevicesに変換」という編集操作を繰り返し実行することができます。
# ↓このへんにカーソルがあるときに`/users`で単語検索
CSV_COLUMNS = [
# ↓この位置に移動するので`cw`してusersを削除し、devicesに編集
app.users.tag_import_columns.number,
app.users.tag_import_columns.target_column, # `n.`で次のusersを検索しつつ繰り返し操作
app.users.tag_import_columns.target_value, # `n.`で次のusersを検索しつつ繰り返し操作
app.users.tag_import_columns.tag, # `n.`で次のusersを検索しつつ繰り返し操作
app.users.tag_import_columns.memo, # `n.`で次のusersを検索しつつ繰り返し操作
]