fnameescape()は使うな

Vim 7.1.299 で fnameescape() なる関数が追加された。これは :edit などファイル名が必要な箇所でファイル名をエスケープしてくれる関数。

例えば

VimM#1.txt と言うファイルを開きたいとする。この場合、

:edit VimM#1.txt

などとしてもうまく開けない。これは # が代替ファイル名(:h alternate-file)として展開されてしまうため。正しく開くためにはエスケープする必要がある。

:edit VimM\#1.txt

こう言った展開されるパターンはいくつかあり、いちいちエスケープするのは面倒。これをやってくれるのが fnameescape() 関数。

:execute 'edit' fnameescape('VimM#1.txt')

スクリプトで使うことが多いので、実際は 'VimM#1' は変数のことが多い。なので余計エスケープのし忘れが発生しやすい。
こんな感じで、 fnameescape() は便利な関数です。まる。

ところが

fnameescape() も完璧ではなかった。以下の例は Windows で発生。 congratulations!.txt と言うふざけた名前のファイルを開きたいとする。

:execute 'edit' fnameescape('congratulations!.txt')

しかし実際に開いたファイルは…

:echo expand('%')
congratulations\!.txt

! がエスケープされてしまった。なんか [新規ディレクトリ] とか出てるし。Windowsだからね。
これではファイル名に ! が入ったファイルをうまく扱えないので、実質 fnameescape() は使えない。もしかしたら他にも余計にエスケープされてしまう文字があるかもしれない。

更に別の問題

コマンドでファイルが与えられるところでは、 全体をバッククォート(`)で囲うことでシェルからファイル名を指定できる(:h backtick-expansion)。
このバックスラッシュは、Linux ではバックスラッシュでエスケープできるが、Windowsではエスケープすることができない。つまり、fnameescape() を使ってもバッククォートで囲われたファイルを開くことが出来ない*1

解決策

実は全てを解決する策がある。 `=...` を使えばいい(:h `=)。
これはVimの式の評価結果をファイル名として使用する。これを使えば、

:edit `='congratulations!.txt'`

これで問題なく開ける。なんと、 :execute すら要らなくなった。

式ならほぼ何でも書ける

半角スペースやバッククォートすら書ける。

:edit `='`example file`'`
:echo expand('%')
`example file`
もちろん関数呼び出しやローカル変数などももろもろ使える
" fuzzyfinder.vim より引用。なにやら頑張っているが…
execute printf('file `=%s`', string(a:buf_name))
" ↓で問題ない
file `=a:buf_name`
注意

もともとの目的なので当然だが、一切の展開が行われなくなるので注意。つまり、 ~ がホームディレクトリに展開されない。
ここは expand() を使って…といいたいところだが、それだと # なども展開されて元も子もないので、この場合は fnamemodify() を使えばよい。

let path = '~/.vimrc'
file `=fnamemodify(path, ':p')`
もちろん :cd や :argadd などにも使える
:cd `='~'` " :cd の場合は ~ や -(直前のディレクトリ) を渡しても問題ない模様
:argadd `='text1.txt'` `='text2.txt'` `='text3.txt'` " 複数渡してもまったく問題ない
さらに
  • `=...` は Vim 7.1.299 以前でも使える。
  • また、 fnameescape() でないと対処できないケースはそう多くない。

結論

fnameescape() ではなく `=...` を使おう!

*1:そんなファイル名使わんとか言う突っ込みはなしの方向で