← Vim研究所

Vimでシェルコマンドの出力をバッファに直接挿入する(:r!)

2019-05-30 公開 ・ 更新: 2026-09-03

ターミナルでlsを実行して、その結果をコピーしてVimに貼り付ける、という作業を繰り返していました。コマンドの実行結果を直接バッファに挿入するコマンドがあり、コピペの手間そのものがいらなくなります。結果がどこへ入るのか、動いているシェルは何なのか、行を置き換える:%!と何が違うのかまでを、Vim 9.1.1752とNeovim 0.12.4で打った結果とあわせて見ていきます。

目次(8項目)
キーこれだけ覚える
:r!{コマンド}コマンドの出力をバッファに挿入
:!{コマンド}結果を画面に表示するだけ(バッファには残らない)
:%!{コマンド}バッファ全体をコマンドに通して置き換える
:0r!{コマンド}ファイルの先頭に挿入(:$r!で末尾)
:new新規バッファを開いてから読み込む

コマンドの実行結果を挿入する

:r!{コマンド}

:r:readの略で、本来はファイルの内容をバッファに読み込むコマンドです。ファイル名の代わりに!とコマンドを書くと、読み込み元がファイルからコマンドの標準出力へ切り替わります。挿入先はカーソルのある行の下で、カーソル行そのものは書き換わりません。:readで別ファイルを読み込む動きの読み込み元だけを差し替えたものなので、後で出てくる++enc=のような修飾子もそのまま効きます。

BEFORE
line 1
line 2
:r!echo hi
AFTER
line 1
hi
line 2

書き方は:r!echo X:r !echo X:r! echo Xの3通りが見かけ上ありますが、どれも同じ意味です。3行のファイルの2行目にカーソルを置いて順に試すと、3通りとも2行目の下にXが1行だけ入りました。:readには:w!のような強制版が無いので、!以降は必ずコマンドとして読まれます。

読み込んだ直後のカーソルは、挿入された最後の行に置かれます。1行目の下に3行を読み込んで位置を調べると、範囲の先頭を指すマーク'[が2行目、末尾を指す']が4行目、カーソルも4行目でした。Neovim 0.12.4でも同じ値です。入れたぶんだけを続けて加工したいときは:'[,']と範囲を書けます。マークで位置を覚えて飛ぶ方法と同じ記法が、自動で付くマークにも使えます。

何行入っても取り消しはuの1回で済みます。:r!は複数行の挿入を1つの変更として記録するので、200行が入っても押すのは1回です。位置を厳密に合わせてから打つより、打ってから戻すほうが速い場面が多いです。

よく使う場面と実行されるシェル

:r!ls        " カレントディレクトリのファイル一覧を挿入(Linux/Mac)
:r!dir       " 同上(Windows)
:r!pwd       " カレントディレクトリのフルパスを挿入

設定ファイルにファイル一覧を書き出したいときや、スクリプトに現在のパスを埋め込みたいときなど、「今いる場所の情報をテキストとして取り込みたい」場面で役立ちます。git log --oneline -5で直近のコミット一覧を貼るなど、決まった形の出力を毎回同じ位置に置く作業ほど効きます。

ここで一度確かめておきたいのが、コマンドを実行しているシェルの正体です。:set shell?を打つとshell=/bin/zshが返りました。環境変数SHELLを引き継いだ値で、SHELLを外して起動し直すとshell=shになります。:set shellcmdflag?-cで、Vim 9.1.1752とNeovim 0.12.4で同じ値でした。

つまり:r!が呼ぶのは対話シェルではなくzsh -cです。.zshrcに書いた別名や関数は読み込まれません。.zshrcだけにhelloという別名を定義して:r!helloを実行すると、入ったのは結果ではなくzsh:1: command not found: helloの1行でした。.zshenvに置いた場合は読まれるので、PATHをどちらに足しているかで結果が変わります。

エラーの文言がそのままバッファに入るのは、'shellredir'の既定値が>%s 2>&1で、標準エラー出力も出力に混ぜて受け取るためです。ターミナルでは通るコマンドがVimから通らないとき、失敗は画面の警告ではなく本文の1行として現れます。打った直後に入った行を読む習慣を付けておくと安全です。

実行されるディレクトリは:pwdが返す場所です。:lcd subでウィンドウのカレントディレクトリを移してから:r!basename $(pwd)を実行すると、挿入されたのはsubでした。編集中のファイルの場所とは限らないので、相対パスを渡すときは:pwdを基準に考えます。

挿入する行を数字で決める

カーソル行の下という既定は手軽ですが、長いファイルの途中で打つと思っていない場所に入ります。位置を確実に決めたいときは行番号を前に付けます。0はファイルの先頭より前、$は最終行の後ろです。3行のファイルで試すと、:0r!echo TOPは1行目の上に、:$r!echo BOTは4行目として入りました。カーソルを動かして確認しなくても、どこに入るかが分かります。

:0r !date +\%Y-\%m-\%d     " ファイルの先頭に挿入
:$r !git log --oneline -5  " ファイルの末尾に挿入
:10r !ls                   " 10行目の下に挿入

紛らわしいのが、行番号ではなく範囲を前置したときの扱いです。:1,3r!echo R:2,3r!echo R2を同じ3行のファイルで実行すると、どちらも3行目の下に1行だけ挿入されました。:readが受け取るのは位置を表す行が1つだけで、範囲を書いても最後の行しか使われません。:%!のように範囲を取るコマンドと見た目が似ているので、選択した行に何かが起きると思って打つと食い違います。

打ったもの挿入された場所
:r!echo Xカーソル行(2行目)の下
:0r!echo TOP1行目の上
:$r!echo BOT最終行の下
:1,3r!echo R3行目の下
:2,3r!echo R23行目の下

コマンドラインでは%が「開いているファイル名」に展開されます。c.txtを開いた状態で:r!date +%Yを打つと、入ったのは年ではなくc.txtYでした。エラーは出ず、それらしくないテキストが静かに入るので気づきにくい失敗です。:r!date +\%Yと打ち消せば2026が入ります。

逆に、この展開を利用する手もあります。:r!wc -l < %と打てば、開いているファイルの行数が挿入されます。macOSのwcは数字の前に空白を詰めて返すので、数値だけが欲しいときはtr -d " "を挟みます。展開の規則は関数のexpand('%')と同じで、%:hでディレクトリだけ、%:rで拡張子を除いた名前を渡せます。開いているファイルへの集計や変換を1行で済ませられます。

:! や :%! との違い

:!{コマンド}は結果を画面に表示するだけで、バッファには何も残りません。3行のファイルで:!echo Xを実行したあと中身を確かめると、行数も内容も変わっていませんでした。一方:r!{コマンド}は出力をテキストとしてバッファへ書き込みます。コマンド実行そのものの選択肢は:! と :terminal の使い分けにまとめています。

3つ目の形が:%!{コマンド}です。こちらは指定した範囲の行をコマンドの標準入力へ渡し、返ってきた出力で同じ範囲を置き換えます。alphaから始まる3行に:%!sort -rを実行するとcharlieが先頭に来る並びになり、:1,2!tr a-z A-Zでは前の2行だけが大文字になりました。ビジュアルモードで選んでから:を押すと、範囲が先に入った状態で始まります。

置き換えである以上、コマンド名を打ち間違えたときの被害は:r!より大きくなります。2行のバッファで:%!nosuchcmd_xyzを実行すると、中身はzsh:1: command not found: nosuchcmd_xyzの1行だけになりました。Vim 9.1.1752でもNeovim 0.12.4でも同じです。標準エラーまで出力として受け取るので、失敗したコマンドの文句が本文を上書きします。uで元に戻るため、慌てて保存しないことだけ覚えておけば足ります。

並べ替えだけが目的なら、外部コマンドを呼ばずに:sortで行を並べ替える方法を使うほうが速く、環境の違いにも左右されません。:%!が生きるのは、jqcolumnのようにVimが持っていない整形をかけたいときです。

向きが逆のコマンドが:w !{コマンド}です。バッファの内容を標準入力へ流し、出力は画面に出るだけでバッファは変わりません。3行のファイルで:%w !wc -lを実行すると3が返り、中身は3行のままでした。

ここでは!を置く位置が意味を持ちます。:rでは:r !ls:r! lsも同じでしたが、:wでは:w !wcがコマンドへの送信、:w! wcwcという名前のファイルへの上書き保存になります。実際に:w! wcを打つと、ディレクトリにwcというファイルができました。:wには強制版があるぶん、スペースが1つずれると別のコマンドとして通ってしまいます。

要点:その場で見るだけなら:!、テキストとして取り込むなら:r!、いまある行を出力で置き換えるなら:%!。取り込む向きの逆が:w !です。
使ってみて

きっかけは、READMEに毎週ディレクトリ構成を貼り直す作業でした。ターミナルに切り替えて一覧を出し、マウスで選択して、Vimに戻って貼り付けて、余計な空行を消す。この4手を週に何度も繰り返していて、あるとき「ファイルを読めるなら出力も読めるのでは」と思い付いて:help :readを引いたのが最初です。効いたのは打鍵数よりも、往復している数秒のあいだに何を貼ろうとしていたか忘れる、という詰まり方が消えたことでした。

大きい出力は別のバッファへ逃がす

コマンドの結果だけをまっさらなバッファで見たいときは、先に:newで新しいウィンドウを開いてから:r!を実行します。元のファイルを汚さずに、出力を眺めてから必要な行だけを持っていけます。見終わったら:q!で閉じれば保存も聞かれません。

:new
:r!git log --oneline -10

これは行儀の問題だけでなく、出力が大きいときの実利もあります。数万行を返すコマンドをそのまま挿入すると、読み込みと再描画で固まったように見える時間が続き、取り消しの履歴にも巨大な変更が1つ積まれます。挿入する前にheadgrepで絞るか、いったんファイルに落としてから開くほうが確実です。

:r !find . -name '*.log' | head -50
:!find . -name '*.log' > /tmp/list.txt
:e /tmp/list.txt

結果を待ちたくないなら、:terminalで走らせておいて、終わってから必要な行だけをコピーする手もあります。

出力の文字コードと改行

外部コマンドの出力がUTF-8以外の場合、挿入した部分だけが化けます。読み込み時の文字コードは++enc=で指定でき、置く場所はコマンドの手前です。cp932で書いた日本語のファイルを:r ++enc=cp932 !cat sjis.binで読み込ませると、化けずに入りました。:e ++enc= で文字コードを指定する方法と同じ修飾子が効きます。

:r ++enc=cp932 !dir        " Windows のコマンド出力を読む

改行のほうは込み入っています。:r!は読み込むたびに改行の形を判定していて、材料になるのが'fileformats'です。Vim 9.1.1752とNeovim 0.12.4のどちらも既定値はunix,dosでした。読んだ行がすべてCRLFで終わっていればdosと見なしてCRを落とし、1行でもLFだけが混ざるとunixと見なして残します。

実際にprintf 'x\r\ny\r\n'を読み込ませると、Vimではxyが入り、CRは残りませんでした。片方だけをCRLFにしたprintf 'x\r\ny\n'ではx^Myになります。:set ffs=unixにすると、どちらの場合もCRが残りました。^Mが付くことも付かないこともあるのは、この判定が読み込みごとに走るためです。

Neovim 0.12.4では結果が違い、CRLFとLFを混在させてもCRが残りませんでした。効いているのは'shelltemp'で、Vimは既定でオン、Neovimは既定でオフです。オンのときは出力をいったん一時ファイルへ落としてから読み込み、オフのときはパイプで直接受け取ります。フィルタに渡る標準入力を調べると、Vimでは通常のファイル、Neovimではパイプでした。Neovimで:set shelltempとしてから同じ実験をするとx^Mが現れます。

読み込んだ出力Vim 9.1.1752Neovim 0.12.4
全行がCRLFCRは消えるCRは消える
CRLFとLFが混在x^Mが残るCRは消える
ffs=unixで全行CRLFx^Mが残る消える(shelltempで残る)
noshelltempにしたVim(混在) CRは消える

残ってしまった^Mは、挿入した範囲だけを狙って消せます。:'[,']s/\r$//と打てば、直前に読み込んだ範囲だけが対象になります。ファイル全体に置換をかけると元からあった行まで巻き込むので、範囲マークを使うほうが安全です。

まとめ

:r!はシェルコマンドの出力をそのままテキストとしてバッファに取り込めます。コピペの手間を減らしたいなら覚えておいて損はないコマンドです。

関連記事

← Vim研究所 トップへ戻る