cmd.exe
/s オプション
/C または /K が指定されている場合、スイッチの後の残りのコマンド ラインが
コマンド ラインとして処理されます。次のルールが引用符 (") の処理に使われます:
1. 次のすべての条件に一致する場合、コマンド ラインの引用符が有効になり
ます:
- /S スイッチがない
- 引用符が 1 組ある
- 引用符の中に特殊文字がない
(特殊文字は &<>()@^| です)
- 引用符の中に 1 つ以上のスペースがある
- 引用符の中の文字列が、実行可能ファイルの名前である
2. 最初の文字が引用符であるにも関わらず上の条件に一致しない場合は、最初
の引用符とコマンド ラインの最後の引用符が削除され、最後の引用符の後
のテキストが有効になります。
/c /k 以降のコマンドラインの両端にある引用符は削除されるのが原則( "a" "b" -> a" "b )
しかし 1 の条件を満たした時だけ、そうならない(引用符が有効になる、というのは preserve されるということっぽい)
いずれにせよ、
つまり、こういうこと
code:a.bat
@echo off
echo a: %1
code:a.bat b\c.bat
@echo off
echo c: %1
code:bat
a.bat b\c.bat d
a: b\c.bat
"a.bat b\c.bat" d
c: d
cmd /c a.bat b\c.bat d
a: b\c.bat
cmd /c "a.bat b\c.bat" d
c: d
(唯一の引用符が実行可能ファイルなので残された)
cmd /s /c "a.bat b\c.bat" d
a: b\c.bat
(唯一の引用符が実行可能ファイルだが、/s がついているので1文字目の引用符と最後の引用符が削除された)
cmd /c "a.bat b\c.bat" "d"
a: b\c.bat" "d
(引用符が複数あるので1文字目の引用符と最後の引用符が削除された)
cmd /c "a.bat b\x"d
a: b\xd
(唯一の引用符が実行可能ファイルでないので、1文字目の引用符と最後の引用符が削除された)
cmd /c "a.bat b\c"d
'"a.bat b\c"d' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
(唯一の引用符が実行可能ファイルなので残されたが、結果は不正なコマンドライン)
引用符のエスケープ
cmd.exeの引用符を要約すると、
(キャレットでエスケープされていない)引用符に遭遇したら次の引用符まで全てのメタ文字を無視する
その処理を経たあとでも引用符が残る
単純にコマンドラインを頭から一文字ずつ読んでやる感じで、スペースとかで区切られない文字列の途中でも効く
ということになる。
引用符はあらゆるメタ文字を無視するので、途中の引用符をエスケープできない。なおかつ引用符が必ず出力に出てしまうので、「引用符を使いたい時だけ文字列リテラルを切る」みたいなこともできず、器用に今のモードを意識しながらエスケープしたりしなかったりするはめになる。…だったらもうメタ文字を全部 ^ でエスケープすることにして、引用符モードは使わないのが単純だろう。
個別エスケープ作戦で空白文字が大丈夫か気になるが、むしろcmd.exeが分割をしないなら、msvcrt向けに ^" で囲めばよい?どうだろう。
あるいは、相手がMSVCのコマンドラインパースに準じているのであれば、引用符モードの中で二重引用符を使うのが簡単。cmd.exe がCreateProcessを呼ぶまでには二重引用符のまま残っているが、その先でどうにかしてもらう。