← Vim研究所

Vimの番号付きレジスタ「"0〜"9」の仕組みを正確に理解する

2019-08-07 公開 ・ 更新: 2026-09-03

数回前に削除した内容を"3pで戻そうとしたら、期待と違う内容が出てきました。番号付きレジスタは「削除するたびに1つずつずれる」という理解だけでは説明できない例外がいくつかあります。

この記事に載せた値は、macOS 26.4.1 上の Vim 9.1.1752 と Neovim 0.12.4 で、どちらも設定を読み込まない状態(-Nu NONE-u NONE)で取ったものです。レジスタの中身は目視ではなくgetreg()の戻り値と:registersの画面をそのまま写しています。プラグインを入れた環境では結果が変わりうる箇所が1つあり、それは例外の節で触れます。

目次(8項目)
キーこれだけ覚える
"0最後にヤンクした内容(削除の影響を受けない)
"1直近の削除・変更(条件を満たすもの)
"2"91つ前の"1〜"8がシフトしたもの
"-1行未満の削除(小削除レジスタ)

基本ルール

レジスタの全体像で触れたとおり、"0はヤンク専用で削除の影響を受けません。"1"9は削除・変更のたびに1つずつシフトしていく履歴で、新しい削除が"1に入ると、それまでの"1"2へ、"2"3へと押し出されます。

BEFORE
line A
line B
line C
│ ← ddで2行削除した直後
"2p
AFTER
line C
line A

2回削除した時点で"1は「line B」ですが、1回目に削除した「line A」は"2にシフトしています。何行か前に消した内容を戻したいときは、番号を1つずつ変えながら:registersで中身を確認すると確実です。ここでは"1"9がどんな条件で更新されるかを正確に見ていきます。

押し出しは9段で止まります。12行のファイルの先頭でddを11回続けたところ、"1には11回目に消した行、"9には3回目に消した行が入り、1回目と2回目に消した行はどのレジスタにも残っていませんでした。無限にさかのぼれる履歴ではなく、直近9件だけを保つ固定長の列だと考えると読み違えません。この列を動かすのは削除だけではなく、ccのような変更コマンドも同じ規則で消した部分を"1へ送ります。上の11回に続けてccを打つと、"1ccが消した行に入れ替わり、直前まで"1にあった行は"2へ移りました。

"0はヤンクした内容だけを覚えている

番号付きレジスタで日々いちばん効くのは"0です。yyでコピーした行を貼る前にddで別の行を消すと、無名レジスタが削除した行で上書きされるので、pで出てくるのは消した行のほうになります。AAA・BBB・CCCの3行で確かめると、yyの直後は無名レジスタも"0も「AAA」でしたが、ddで「BBB」を消した時点で無名レジスタは「BBB」に変わり、"0は「AAA」のままでした。pの代わりに"0pと打てば、途中で何行消していても最後にヤンクした内容が出ます。

差がいちばんはっきり出るのは、ヤンクした語で別の語を次々に置き換える操作です。yiwで「NEW」をヤンクしてからviwpで「foo」を置き換えると、押しのけられた「foo」が無名レジスタに移ります。そのまま次の行でviwpを打つと、貼られたのは「NEW」ではなく「foo」でした。同じ手順をviw"0pに変えて3行ぶん繰り返したところ、3行とも「NEW」に置き換わります。レジスタの貼り付け方を覚えるとき、この1つだけは先に手に入れておく価値があります。

"0が更新される条件はひとつだけで、レジスタを指定しないヤンクです。手元でyyのあとに"ayyで別の行をヤンクしても、"0は最初にヤンクした行を保持したままでした。名前付きレジスタへ退避したつもりで"0pを打つと意図より古い内容が出てくるのは、この条件によります。

原則:1行以上の削除だけが対象

基本ルールでは、削除・変更した範囲が1行以上のときだけ"1に入ります。xや行内のdwのように1行未満の削除は、"1ではなく小削除レジスタ"-に入ります。ここまでは多くの人が理解している範囲です。

実際に打つと線引きははっきりします。「four delta」の行頭でxを押すと"-は「f」になり、"1は直前のddで入った「three gamma」のまま動きません。続けてdwを押しても"-が「our 」に変わるだけで、"1"2は据え置きでした。この線引きのおかげでxを連打しても削除履歴が流れません。9個しかない枠を1文字の削除で埋めてしまうと、数手前に消した行を取り戻すという本来の用途が成立しなくなります。

ただし"-には「レジスタを明示した削除では使わない」という条件が付いています(:help quote-)。"axのようにレジスタを指定して1文字消すと"-は更新されず、行き場を失ったぶんの扱いでVimとNeovimが割れました。

操作削除の範囲Vim 9.1.1752Neovim 0.12.4
x行内・レジスタ無指定動かない動かない
"ax行内・レジスタ指定"1に入る動かない
"add行単位・レジスタ指定"1に入る"1に入る

起動直後のまっさらな状態で「alpha bravo」の行頭にカーソルを置き、"axだけを打った結果です。Vimでは"1に「a」が入り、Neovimでは空のままでした。:help quote_numberには「1行未満なら小削除レジスタが使われる」と「レジスタを指定していても"1は埋まる」という2つの条件が並んで書かれていて、その両方が同時に効く"axのような場合にどちらを優先するかまでは書かれていません。行削除の"addならどちらの実装も"1を更新するので、差が出るのは行内削除にレジスタを指定したときだけです。

逆に履歴をまったく汚したくない削除には"_を使います。"_ddで行を消しても"1は動かず、"2以降も押し出されませんでした。数手前の削除を取り戻したいのに、その前に不要な行を何十行も消す必要があるときは、消す側だけ"_ddにしておくと履歴が守れます。

例外:特定のモーションは1行未満でも"1に入る

ややこしいのはここからで、次のモーションと組み合わせた削除は、結果が1行未満であっても"-だけでなく"1にも入ります。

%   " 対応する括弧まで
(   " 前の文へ
)   " 次の文へ
`   " マークへのジャンプ
/   " 検索(前方)
?   " 検索(後方)
n   " 次の検索マッチ
N   " 前の検索マッチ
{   " 前の段落へ
}   " 次の段落へ
削除の範囲使ったモーション"-"1
1行未満通常(x dw など)入る入らない
1行未満上記の特殊モーション入る入る
1行以上(複数行にまたがる)どれでも入らない入る

たとえばd/foo<Enter>で検索位置までを削除した場合、その範囲が1行に収まっていれば"-"1の両方に同じ内容が入ります。一方、検索先が別の行で削除範囲が複数行にまたがると、通常の複数行削除と同じ扱いになり"1だけが更新されて"-は変化しません。「特殊モーション="-に入らない」ではなく、「行をまたぐかどうか」で"-の有無が決まる点を押さえておくと安心です。

「alpha beta gamma delta」で始まる4行のファイルで打った値がこうでした。行頭からd/gammaEnterで行内を消したときは"1"-の両方が「alpha beta 」になります。同じ位置からd/secondで次の行まで消すと"1だけが更新され、"-は直前の「alpha beta 」を持ったままでした。段落末までのd}も同じで、"1に2行ぶんが入り"-は動きません。

この例外はVimが後から足した親切ではなく、:help quote_numberに「this is Vi compatible」と明記されているとおりViから受け継いだ仕様です。実務でも筋は通っていて、検索先まで一気に消すd/や段落単位のd}は消える量が事前に読めないぶん「消しすぎた」が起きやすく、量に関係なく"1に残ってくれると助かります。ただし同じヘルプは、このモーションの一覧について写像されている可能性があると断っていて、matchitプラグインが%を置き換える例を挙げています。手元では素の状態でしか確認していないので、プラグインを入れている環境では:registersで実際の入り方を見てください。

:registersで実際に確認する

理屈だけで覚えようとせず、:registers(省略形:reg)で実際にどのレジスタに何が入っているかを確認しながら操作すると理解が早まります。何度か削除や検索絡みの操作を試して、"1"-のどちらに何が入るかを目で確認しておくと、ルールを暗記しなくても感覚で判断できるようになります。正確な条件は公式ヘルプ(:h registers)にも記載があります。

見たいレジスタだけを引数で絞れます。yyのあとddを3回打った直後に:registers 0 1 2 3 4 - "と入力すると、次の画面が出ました。

Type Name Content
  l  ""   three^J
  l  "0   one^J
  l  "1   three^J
  l  "2   two^J
  l  "3   one^J

Nameが番号、Contentが中身で、^Jが改行を表します。左端のTypeは貼り付けの単位で、lなら行単位です。引数に入れた"4"-が表示されていないのは、空のレジスタが省かれるためで、指定を間違えたわけではありません。スクリプトから1つだけ覗きたいときは:echo string(getreg('1'))が手軽で、単位まで知りたいときはgetregtype()が使えます。

番号を手で増やさずに済む方法もあります。"1pで貼ったあとに.を打つと、Vimは"1を貼り直すのではなく"2を貼り、もう一度.を打つと"3を貼ります。3行を消したファイルで"1pのあと.を2回打つと3行とも戻り、これはVim 9.1.1752とNeovim 0.12.4のどちらでも同じでした。

戻る並び順だけは注意が必要です。先頭行でddを3回打って「one」「two」「three」の順に消した場合、この方法で並ぶのは「three」「two」「one」で、元の並びとは逆になりました。末尾から上へddを3回打った場合は「one」「two」「three」の順に戻ります。消しながら下へ進んだのか上へ戻ったのかで結果が変わるので、貼り終えたら並びを目で確かめてください。

実務での影響

この違いを知らないと、「複数行にまたがる検索絡みの削除を"-pで戻そうとしたら見つからない」という混乱につながります。検索やジャンプを伴う削除を取り消したいときは、まず"1pを試し、それでも見つからなければ番号を1つずつ増やしながら探す、という手順を覚えておくと安心です。番号付きレジスタは自動的に内容がシフトしていくので、特定の内容を確実に残しておきたいなら名前付きレジスタに退避させたほうが安全です。

無名レジスタをシステムのクリップボードにつなぐ設定を入れている人は多いと思いますが、番号付きレジスタはその影響を受けません。set clipboard=unnamedの状態でyyddを打つと、無名レジスタと"*は同じ内容になる一方、"0にはヤンクした行、"1には削除した行が別々に入りました。クリップボード連携を有効にしていても、番号を明示した貼り付けはそのまま使えます。

要点:1行未満の削除は基本的に"-に入ります。検索・ジャンプ系のモーションと組み合わせた場合は"1にも入りますが、削除範囲が複数行にまたがった瞬間に"-は更新されなくなります。「見つからなければ"1」と覚えておくと迷いません。
使ってみて

検索でかなり先の行までまとめて削除した内容を、後から"-pで復元しようとして見つからず、同じキーを何度も押しては首をかしげたことがあります。そのときは自分の記憶違いを疑って、そもそも消していなかったのではないかと元のファイルを見に戻りました。しばらくしてから:registersを開いて丸ごと残っているのを見つけたときは、無駄にした10分ぶんの脱力がありました。

まとめ

番号付きレジスタは単純な「1行以上か未満か」だけでなく、モーションの種類によっても振り分けが変わります。検索・ジャンプ系のモーションを使った削除は"1を優先して探すと確実です。

関連記事

← Vim研究所 トップへ戻る