← Vim研究所

Vimでカーソル下の単語を検索する(* と #)

2020-02-09 公開 ・ 更新: 2026-09-03

同じ変数名がファイルの中に何箇所あるか確認したいだけなのに、/を押してから変数名を入力し直していました。カーソルが乗っている単語をそのまま検索してくれるコマンドがあり、これを使えば入力の手間がまるごと省けます。/を打って探す通常の検索そのものは文字列検索の基本(/ と ?)にまとめています。複数ファイルやファイル名まで含めた道具の一覧は検索まとめにあります。

目次(8項目)
キーこれだけ覚える
* / #カーソル下の単語を完全一致で下方向 / 上方向へ検索
g* / g#境界なしの部分一致で下方向 / 上方向へ検索

カーソル下の単語を検索する

*   " 下方向(前方)へ検索
#   " 上方向(後方)へ検索

カーソルが単語の上にあれば、その単語をそのまま検索パターンとして使います。押した瞬間に何が起きたかは、検索レジスタ@/を覗くと分かります。Vim 9.1でuser = 1という行の1桁目にカーソルを置いて*を押し、:echo @/を実行したときの返りがこれです。

:echo @/
\<user\>

打ち込んだ覚えのない\<\>が付いています。これは単語の先頭と末尾を表す正規表現で、*は単語を拾うだけでなく、その両端に境界を足したパターンを組み立ててから検索を実行しているということです。user_idusernameにヒットしないのはこの2文字のおかげで、逆に言えば/userと手で打った検索とは別物になります。

カーソルが単語の外にあるときの動きも実測しました。 user hereという行(行頭に半角空白4つ)の1桁目でも*は無反応にならず、@/には\<user\>が入り、カーソルは次の一致へ移りました。名前は「カーソル下の単語」ですが、実際には「カーソル位置から右へ見ていって最初に見つかる単語」を拾います。インデントの深いコードで行頭にカーソルを置いたまま押しても効くのはこのためです。

右に何も無ければ検索は始まりません。空白だけの行の1桁目で*を押すとE348: No string under cursorが出て、@/もカーソル位置も変わりませんでした。エラーが出たときは「単語の上に乗っていない」ではなく「その行のカーソル以降に単語が1つも無い」と読み替えると原因を探しやすくなります。

*はジャンプコマンドに分類されているので、押す直前の位置がジャンプリストに積まれます。モーションとしても働き、y*で次の一致までをヤンクできます。\<user\>が1行目・8行目・11行目にあるファイルの1行目で*を2回押してから:jumpsを見ると、こう記録されていました。

:jumps
 jump line  col file/text
   2     1    0 user = 1
   1     8    0 user again
>

ここからCtrl-oを押すたびに8行目、1行目と戻れました。直前の1回だけ取り消したいなら``(バッククォート2つ)でも同じ位置に戻れて、こちらは検索レジスタを触らないのでnで続きを追う流れを壊しません。ジャンプリストの読み方はマーク機能で好きな位置にジャンプする方法で扱っています。

回数を前置きすると、その数だけ先の一致へ飛びます。先ほどの3箇所あるファイルの1行目で3*を押すと、8行目・11行目を通り越してファイル末尾で折り返し、1行目に戻ってきました。'wrapscan'が既定でオンなので、回数が多すぎても止まらずに一周します。

単語の切り出しを決めるのは iskeyword

どこからどこまでを1つの単語と見るかを決めているのは'iskeyword'です。既定値はVim 9.1でもNVIM 0.12.4でも同じ@,48-57,_,192-255でした(:set iskeyword?で確認)。@が英字、48-57が数字、_がアンダースコア、192-255がラテン文字の記号付き文字を表します。-$.もこの中に入っていません。

カーソルを各行の1桁目に置いて*を押し、@/を読んだ実測値が次のとおりです。

行の内容カーソル@/ の値
user_id = 2u\<user_id\>
foo-bar = 4f\<foo\>
$var = 5$\<var\>
@media screen@\<media\>

アンダースコアはキーワード文字なのでuser_idは丸ごと1語として拾われます。一方でハイフンは違うので、foo-barfoobarの2語に割れ、*が検索するのは\<foo\>だけでした。CSSのクラス名やLisp系の識別子を追っているときにヒット数が合わないのは、たいていこれが原因です。

$var@mediaの行では、カーソルが記号の上にあっても右へ滑ってvarmediaを拾いました。実測では$の上(1桁目)で押したあとのカーソルが同じ行の2桁目、つまりvの上に移っています(他に一致が無いので折り返して自分自身に戻った形です)。PHPやシェルスクリプトで「変数名を記号ごと検索したつもりが、名前だけ検索していた」という取り違えがここで起きます。

ハイフンを単語に含めたいなら設定を足します。:set iskeyword+=-を実行してから同じfoo-barの行頭で*を押すと、@/\<foo-bar\>に変わりました。

" CSSとSCSSでだけハイフンを単語の一部として扱う
autocmd FileType css,scss setlocal iskeyword+=-

ただし'iskeyword'*専用のつまみではありません。wdawのような単語移動と単語テキストオブジェクトも同時に変わるので、全体に効かせると別の場所で違和感が出ます。バッファ単位の設定なので、上のようにファイルタイプで絞るほうが事故が少なくなります。

部分一致で検索する:g*/g#

g*   " 下方向、記号を含む部分一致
g#   " 上方向、記号を含む部分一致

通常の*/#は単語の境界(\<\>)を自動的に付けて完全一致で検索しますが、gを前置きすると境界を付けずに検索します。違いはそこだけで、同じuserの上でg*を押したあとの@/userという4文字そのものでした。カーソルが独立した単語userの上にあるとき、*は同じ独立したuserしか対象にしませんが、g*ならuser_idのようにuserを含む他の単語にもヒットします。

BEFORE
user = 1
user_id = 2
username = 3
g*
AFTER
user = 1
user_id = 2
username = 3

同じ場面で*を実行しても、独立したuserは他に存在しないため、カーソルは動きません(一致なしとして自分自身に戻ります)。

#g#は向きが逆になるだけです。userが1・3・5行目にあるファイル(3行目はuser_id = 2)の5行目で#を押すと、@/に入るのは\<user\>*のときと同じです。向きを覚えているのはv:searchforwardのほうで、#のあとは0になります。カーソルは3行目を飛ばして1行目へ移りました。同じ位置でg#ならuserだけが入り、3行目で止まります。このときv:searchforwardが0になるので、以降のnは上方向、Nが下方向へ進む点だけ頭を切り替えてください。

単語以外の範囲を検索パターンにしたいとき

*が見ているのはあくまで「単語」なので、user.nameのように区切りをまたいだ文字列や、単語の途中の数文字だけを検索パターンにはできません。範囲を自分で指定するならビジュアルモードを使いたくなりますが、ここでVimとNeovimの挙動が割れます。

user = 1の行頭でvllと押してuseの3文字を選び、そのまま*を押した結果を両方で見ました。Vim 9.1では@/\<user\>になり、選択した3文字は完全に無視されてカーソル下の単語が使われます。カーソルもuser_idの行を素通りして、次の独立したuserへ飛びました。

NVIM 0.12.4では同じ操作で@/\Vuseになり、カーソルはuser_id = 2の行に止まりました。選択範囲がそのまま検索パターンになっています。頭の\Vはvery nomagicの指定で、選んだ文字列に.*が混ざっていても正規表現ではなくただの文字として扱わせるためのものです。

この差はエラーにならないので静かに効きます。Neovimの指の動きをVimで再現しようとすると、選んだつもりのない単語で検索が走ります。Vim側で同じ動きが欲しければ、選択範囲をヤンクして検索コマンドラインへ流し込むマッピングを足します。手元のVim 9.1で次の2行を:sourceして確かめました。

" 選択範囲をそのまま検索パターンにする(Vim用)
xnoremap * y/\V<C-r>=escape(@", '/\')<CR><CR>
xnoremap # y?\V<C-r>=escape(@", '?\')<CR><CR>

user.name = 3の行でuser.nameの9文字を選んで*を押すと、@/\Vuser.nameになりました。escape()で区切り文字とバックスラッシュを逃がしているので、パスやURLのように/を含む文字列を選んでも検索コマンドラインが途中で切れません。

使い分けの目安

状況おすすめ
同じ変数名を正確に追いたい*/#
接頭辞・接尾辞が同じ単語もまとめて追いたいg*/g#

選んだキーとは別に、大文字小文字の扱いは検索オプション側が握っています。'ignorecase'をオンにした状態でFOO = 6の行頭から*を押すと、@/は大文字のまま\<FOO\>なのに、カーソルは小文字のfoo = 7の行へ移りました。パターンの見た目と実際の照合が食い違うので、レジスタだけ見て「大文字で検索している」と判断すると読み違えます。

そこで'smartcase'を足せば大文字入りは厳密に照合される、と思うと外れます。:set ignorecase smartcaseにして同じFOO*を押しても、Vim 9.1・NVIM 0.12.4のどちらもfoo = 7の行に止まりました。'smartcase'/?で打ち込んだパターンにだけ適用される規則で、*が内部で組み立てたパターンは対象外です。厳密に追いたいなら/\C\<FOO\>のように\Cを明示するか、'ignorecase'を一時的に切ります。実際に\Cを付けて検索したら、foo = 7を飛ばしてFOO againの行に着きました。

ハイライトの既定値も揃っていません。'hlsearch'はVim 9.1では0、NVIM 0.12.4では1でした。つまりVimで*を押しても、設定していなければ一致箇所は色が付かずカーソルだけが飛びます。:nohlsearchv:hlsearchが0になった状態から*を押すと1に戻りました。消したはずのハイライトが検索のたびに復活する仕組みと、その止め方は検索ハイライトを消す設定にまとめています。

使ってみて

SCSSのファイルでbtn-primaryというクラス名の上で*を押し、「3箇所しか使っていないから消していい」と判断してまとめて削除したことがあります。実際にはbtnだけが検索されていて、残っていた定義ごと巻き込みました。押した本人にはbtn-primaryを検索したつもりしかないので、画面のハイライトを見ても違和感が湧かなかったのが怖いところでした。

検索してから編集につなげる

*@/を書き換えてくれるので、そのまま置換につなげられます。:substituteのパターンを空にすると直前の検索がそのまま使われる決まりなので、userの上で*を押してから:%s//USER/gと打つだけで済みます。実測では1行目のuser = 1と5行目のuser againだけが書き換わり、user_id = 2はそのまま残りました。

:%s//USER/g

USER = 1
user_id = 2
USER again

境界付きのパターンが引き継がれているので、置換の側で\<\>を書き直す必要がありません。この省略の細かい形や確認付き置換は:substituteコマンド活用集で扱っています。

1箇所ずつ見ながら直したいときはcgnです。*で検索したあとに実行すれば、次の一致を選んで書き換えモードに入れます。2箇所目からはnで送って.で繰り返せます。動きの詳細は直前の検索キーワードをそのまま変更するcgnにあります。

関数の途中で「この変数がどこに散っているか色だけ見たい」ときに*を押すと、画面が別の場所へ飛んでしまいます。検索レジスタを直接書くマッピングにすれば、カーソルを置いたままハイライトだけ点きます。

" カーソルを動かさずにカーソル下の単語をハイライトする
nnoremap <silent> * :let @/ = '\<' . expand('<cword>') . '\>'<Bar>set hlsearch<CR>

これを:sourceしてからuser_id = 2の1桁目で*を押すと、@/\<user_id\>v:hlsearchは1になり、カーソルは1桁目に残りました。検索そのものを実行していないのでgetjumplist()の件数も増えません。

本来の*の動きは残したまま、ジャンプリストだけ汚したくないなら:keepjumpsを使う手もあります。:clearjumpsのあとggで1件積まれた状態から:keepjumps normal! *を実行するとジャンプリストは1件のままで、素の*では2件に増えました。ただしカーソルは普通に飛ぶので、戻る手段はCtrl-oではなく``になります。

まとめ

*#\<\>を足した完全一致、g*g#は足さない部分一致です。どちらが走ったかは:echo @/で必ず確認できるので、ヒット数が思ったとおりにならないときはまずここを見ます。値が\<foo\>のように途中で切れていたら、犯人は'iskeyword'です。

選択範囲をそのまま検索できるのはNVIM 0.12.4だけで、Vim 9.1ではxnoremapを足さないと同じ動きになりません。検索したあとは@/が残るので、:%s//…/gにもcgnにもそのまま渡せます。

ここで扱ったキーは、ブラウザ上で動くVim道場の検索でジャンプするでそのまま試せます。インストールもログインも要りません。

関連記事

← Vim研究所 トップへ戻る