Windows上でVimからプログラムに引数を渡したい時の注意点
Vim Advent Calendar 2013 の 63日目の記事です。
vim_useに「Escaping for system()」というスレッドがあったので、
Windows上でVimからプログラムに引数を渡したい時の注意点を簡単にまとめて返信したので、日本語でも書いてみます。
Vim側の問題
Windowsでは、'shellslash'オプションがオンの時、shellescape()は文字列をシングルクォートで囲って返します。
しかしcmd.exeはダブルクォートを使うので、シングルクォートで囲っても単純にシングルクォートがそのまま渡されてしまいます。
なので私はWindowsかつ'shellslash'オプションがオンの時でも期待通りに動くshellescape()関数を独自実装して使っています。
vital.vim/Process.vim at 9159770df1ba4b7b63da7a20fa7906bf66f6ebea · vim-jp/vital.vim · GitHub
Windows側の問題
Windowsは複数の引数をWin32APIレベルでサポートしていません。
どういう事かと言うと、新しいプロセスを開始するCreateProcess()関数は、プログラム名と引数を含めた単一のコマンドライン文字列を渡すよう設計されています。
例えばtest.txtというファイルをメモ帳で開きたい場合、CreateProcess()関数には"notepad.exe test.txt"という単一の文字列を渡します。
こういった設計なので、Windows上で実行されるプログラムは引数をパースしなければなりません。
しかしこのパース処理はスタートアップルーチンと呼ばれるものによって処理され、
スタートアップルーチンは大抵コンパイラによって自動生成される*2ため、
普通この処理を書く必要に駆られる事はありません。
しかしここで2つ問題があります。
- スタートアップルーチンを独自の解釈で実装しているプログラムがある
- cmd.exeとCreateProcess()のパース処理のソースコードがクローズドな事
cmd.exeとCreateProcess()のパース処理のソースコードがクローズドな事
クローズドなので正確な処理を確認する事ができません。
もし思い通りの引数が渡ってこない場合、疑うべき3つの箇所
今回はVimのsystem()関数に関連するスレッドのため、
cmd.exe上からプログラムを起動する前提で話します。
この場合、3つ疑うべき箇所があります。
それは起動順で言うと以下の3つです。
- cmd.exe
- CreateProcess()
- スタートアップルーチン
流れとしては以下のような流れになります。
cmd.exe -> CreateProcess() -> スタートアップルーチン -> [プログラムが引数にアクセスする]
この3つの箇所のどこかで間違った解釈がなされているはずです。
ただ、分かったからといって対処できるかどうかは別ですが…
先程も言ったようにcmd.exeとCreateProcess()のパース処理のソースコードはクローズドなため、
疑う事しかできない場合もあります。
ただどの時点でまずい事になっているか分かればどのようなエスケープ処理をするか、
等の対策が立てられる…場合もあります(どうしようもない場合もある)。
関連リンク
- cmd.exe のコマンドラインの仕様を解析してみた - 永遠に未完成
- cmd.exeの挙動について詳細に調査した大掛かりな記事