Vimで別ファイルの内容をカーソル位置に読み込む(:read)
テンプレートやライセンス表記を、いま開いているファイルの決まった場所へ差し込みたいことがあります。別ファイルを開いて選択してコピーして戻ってくる代わりに、:readなら1回のコマンドで済みます。読み込んだ中身がどの行の下に入るのか、そのあとカーソルはどこにいるのか、文字コードや改行コードの違うファイルを読むと何が起きるのかまで、Vim 9.1.1752とNeovim 0.12.4で実際に打った結果とあわせてまとめます。
目次(8項目)
| コマンド | これだけ覚える |
|---|---|
:r {ファイル} | カーソル行の直後にファイルの中身を挿入 |
:0r {ファイル} | 1行目の前に挿入 |
:$r {ファイル} | 最終行の後ろに挿入 |
:r | 引数を省くと編集中のファイル自身を読む |
:r ++enc=cp932 {ファイル} | 文字コードを変換して読む |
基本の使い方
ノーマルモードから次のいずれかを実行します。:readを縮めた:reと:rはまったく同じコマンドで、どれを打っても入る場所は変わりません。
:read {ファイルパス}
:re {ファイルパス}
:r {ファイルパス}
指定したファイルの中身は、カーソルがある行の直後に行として挿入されます。カーソル行そのものは書き換わらず、元からあった行が減ることもありません。置き換えではなく差し込みなので、読み込む前にカーソル行を空けておく必要もありません。
line1
line2
:r header.txtline1
header
line2
パスは絶対でも相対でも受け付けます。ただし相対パスの基準は編集中のファイルの置き場所ではなく、:pwdが返すカレントディレクトリです。別のディレクトリのファイルを開いて作業しているときにここを取り違えると、あるはずの雛形が見つからないという形で詰まります。
:r ~/templates/header.txt
:r ./snippets/license.txt
名前が似ているせいで:editの仲間だと思われがちですが、:readはバッファを開き直しません。2行のa.txtを開いた状態で200行のbig.txtを:rすると、バッファ名はa.txtのまま202行になり、一覧に並ぶバッファも1つのままでした。同じファイルを:e big.txtで開くとバッファ名が入れ替わり、バッファは2つに増えて行数は200になります。読み込みは編集中のバッファに文字を足す操作で、ファイルを開く操作ではありません。区別が曖昧なままだとバッファ一覧の読み方も掴みどころがなくなります。
引数を省いた:rにも意味があります。読み込み元の既定値は編集中のファイル自身で、Z1とZ2の2行を開いて1行目で:rだけを打つと、Z1・Z1・Z2・Z2の4行になりました。直前に開いていたファイルを読みたいときは:r #です。ファイル名を打たずに済むぶん、同じ内容を続けて重ねたいときに手数が減ります。
挿入位置とカーソルの行方
コマンドの手前には、入れたい位置を行番号で書けます。0は1行目の前、$は最終行の後ろを指し、数字を書けばその行の直後です。カーソルを動かしてから打つより、位置を数字で名指しするほうが結果を読み違えません。
:0r ~/templates/header.txt " ファイルの先頭に挿入
:$r ~/templates/footer.txt " ファイルの末尾に挿入
:10r notes.txt " 10行目の直後に挿入
読み込んだあとカーソルがどこにいるかは、続けて打つキーが変わるので覚えておくと効きます。3行のファイルの2行目で2行のファイルを:rすると、カーソルは挿入された先頭の行、つまり3行目に置かれました。VimでもNeovimでも同じ値です。ところが:r !でコマンドの出力を読んだ場合は最後の行である4行目に移り、同じ:readでありながら読み込み元によって行き先が入れ替わります。シェル側の挙動は:r!でコマンド出力をバッファに挿入する記事が担当していて、この記事はファイルを読む側だけを扱います。
挿入された範囲には'[と']のマークが自動で付きます。上の例では'[が3行目、']が4行目でした。マークで位置を覚えて飛ぶ方法と同じ記法がそのまま範囲指定に使えるので、読み込んだぶんだけを狙って加工できます。雛形に説明用のコメント行が混ざっているときや、ログから必要な行だけ残したいときに手数が減ります。
:'[,']g/^#/d " 読み込んだ範囲のコメント行だけ消す
:'[,']v/ERROR/d " 読み込んだ範囲のうち ERROR 以外を消す
:'[,']> " 読み込んだ範囲だけ1段下げる
読み込みは何行入っても1回の変更として記録されます。2行のバッファに200行のbig.txtを読み込んだ直後にundotree()を見るとseq_curは1で、uを1度押しただけで2行に戻り'modified'も下りました。200回押す必要はありません。間違ったファイルを読んでも消し残しは出ないので、undoとredoの使い方を知っていれば、迷う前に打ってしまうほうが早く済みます。
:r {ファイル}は開き直しではなく挿入です。入った範囲は'[と']で指し直せて、取り消しは何行でもu1回で足ります。ファイル名の補完で入力を減らす
:rの後ろではファイル名の補完が効きます。:r templates/まで打ってTabを押すとheader.txtが補われ、画面下にはheader.txt license.txtと候補が並びました。もう一度Tabを押すとlicense.txtへ移ります。補わずに候補だけ見たいときはCtrl-dで、コマンドラインの内容はそのままに一覧が出ました。
候補の出方を決めるのは'wildmenu'と'wildmode'です。Vim 9.1.1752は-u NONEで設定を読まずに起動してもwildmenuが有効、wildmodeはfullで、Neovim 0.12.4も同じ値でした。人の環境や共用サーバーでも、設定を足さずにこの操作が通ります。
読み込む雛形が決まっているなら、パスごとマッピングに畳んでしまうほうが手早く済みます。
nnoremap <leader>th :0r ~/templates/header.txt<CR>
:r! との違いと、一部だけ読みたいとき
!が1つあるかどうかで読み込み元が入れ替わります。:r {ファイル}はファイルの中身を、:r! {コマンド}はコマンドの標準出力を挿入します。挿入という動きは共通で、違うのはどこから読むかだけです。実行されるシェルの正体や、失敗したコマンドの文言がそのまま本文に入る話はシェルコマンドの出力を挿入する:r!にまとめてあります。
| コマンド | 挿入されるもの |
|---|---|
:r {ファイルパス} | 指定したファイルの中身 |
:r! {コマンド} | コマンドの実行結果 |
できないこともはっきりしています。:readにはファイルの一部だけを読む機能がありません。手前に付ける数字は「バッファのどこへ入れるか」を指すもので、「ファイルのどこを読むか」ではないからです。30行のバッファで:10,20r big.txtと打つと、範囲の最後の行である20行目の直後に、30行のbig.txtが丸ごと入って60行になりました。範囲を書いても読む量は変わりません。
やり方は2つあります。読む前に外部コマンドで絞るか、全部読んでから範囲マークで削るかです。前者は絞り込みの記法をシェル側に任せられ、後者はVimの中だけで完結します。ログのように行数が読めないファイルは前者、雛形のように中身が分かっているファイルは後者が扱いやすく感じます。
:r !sed -n '10,20p' big.log " 外部コマンドで絞ってから読む
:r big.log " いったん全部読んで
:'[,']v/ERROR/d " 読んだ範囲だけ絞り込む
なお、ファイルの中身ではなくパスの文字列そのものを本文に書きたいだけなら、読み込みではなく挿入モードの補完が向いています。Ctrl-x Ctrl-fでファイルパスを補完・挿入する方法が別記事にあります。
文字コードと改行コードの扱い
読み込むファイルの文字コードが編集中のバッファと違うと、入った部分だけが化けます。UTF-8のバッファにcp932で書いた日本語のファイルを素で:rすると、<93>ú<96>{のようなバイト列が並びました。'fileencodings'の既定値がVim 9.1.1752もNeovim 0.12.4もucs-bom,utf-8,default,latin1で、cp932が候補に入っていないためです。同じファイルを++enc=cp932付きで読み直すと、日本語がそのまま入りました。修飾子の書き方は:e ++enc= で文字コードを指定して開き直す方法と共通です。
:r ++enc=cp932 legacy.txt " Shift-JIS のファイルを変換して読む
:r ++ff=dos win.txt " 改行コードを指定して読む
改行コードのほうは、読み込むたびに'fileformats'を材料に判定が走ります。どういう条件でCRが落ちるかはシェルの出力を挿入するときの改行の扱いで同じ判定を扱っているので、そちらに譲ります。この記事で見ておきたいのは、判定の結果がバッファ側に残らないことです。
もう1つ、バッファ側の'fileformat'は最初に開いたファイルのまま動きません。CRLFのdos.txtを開いてfileformat=dosになったバッファにLFだけのunix.txtを読み込み、別名で保存してからod -cで覗くと、あとから読んだ行も含めて全4行が\r\nで終わっていました。読み込んだ側が保存時にバッファの流儀へそろえられる形なので、混ざった状態がファイルに残るわけではありません。
| 読み込んだファイル | 読み込み直後 | 保存したファイル |
|---|---|---|
| 全行LF(dosのバッファへ) | そのまま | CRLFで保存 |
新規ファイルにテンプレートを自動で読み込む
毎回同じ雛形を:0rで読んでいるなら、ファイルを作った時点で読ませられます。BufNewFileが発火するのは、まだ存在しないファイル名でバッファを開いたときだけです。既存のファイルを開いても動かないので、中身を押しつぶす事故になりません。
autocmd BufNewFile *.sh 0r ~/.vim/templates/sh.txt
autocmd BufNewFile *.html 0r ~/.vim/templates/html.txt
この書き方には一手足りません。新規バッファはもともと空の1行を抱えていて、0rはその手前に読み込むので、雛形の下に空行が1行残ります。2行の雛形で試すと、VimでもNeovimでも3行目が空のまま残りました。保存すれば末尾に空行が1つあるファイルができあがるので、最終行を消すところまで書いておきます。
autocmd BufNewFile *.sh 0r ~/.vim/templates/sh.txt | $delete _ | 1
$delete _で最終行を無名レジスタへ送らずに消し、続く1でカーソルを先頭へ戻しています。雛形の中に{{cursor}}のような目印を置いておき、読み込んだあと/{{cursor}}で飛んで消す形にすると、書き始める位置まで決まります。
ライセンス表記を~/templates/に置き始めたのは、同じ文言を3つのリポジトリに少しずつ違う形で書いてしまい、あとから差分を突き合わせる羽目になったのがきっかけでした。読み込むコマンドを覚えるより、雛形の置き場所と名前を決めるほうに時間がかかっています。今は拡張子と同じ名前でそろえていて、sh.txtやhtml.txtのように迷わず引けます。
まとめ
ファイルの中身をそのまま差し込むのが:readです。思った場所に入らないときは手前に付けた行番号を、入った文字が化けるときは++enc=と'fileencodings'を、末尾に余計な行が残るときは読み込む側ではなくバッファがもともと持っていた空行を疑うと、原因の見当が早く付きます。