Windowsのコマンドプロンプトでワンライナーを書こうとすると二重引用符がビョーキ
Windowsのコマンドプロンプト(cmd.exe)でコマンド引数に空白を含む文字列を渡したいときは二重引用符でくくります。ここまではよく知られたノウハウ。
では、二重引用符を含む文字列を渡したいときは?、となると大半の人はあやしいはず。「¥でエスケープすればいい?」、いえ、¥がエスケープ文字になるのはUNIX系のシェルです。cmd.exeでのエスケープはどうなるかというとこれが公式情報が出ていない上に、どうもその隠し仕様も相当複雑なようで、こちらの記事
を読んでみても頭がくらくらするのですが、この知見を下敷きに、最近見つけた面白い動作の例をひとつご紹介します。
正解
二重引用符がまさかの入れ子で解釈されます。
> cmd /c "set aaa=" bbb "&set aaa"
子プロセスとして呼ばれたcmd.exeにコマンドとして
> set aaa=" bbb "&set aaa
が渡され、&は連続実行ですから
> set aaa=" bbb " > set aaa
と実行したのと同じこと。結果として、子プロセスcmd.exeの環境変数aaaに「" bbb "」が格納されてそれをすぐ呼び出すわけですから、出力として
" bbb "
が表示されます。
入れ子で解釈される仕組み
まず、最初のコマンドが&という文字を含んでいるのにここで区切られて2つのコマンドの連続実行だと解釈されてしまわない理由です。コマンドプロンプトは&やパイプ(|)、リダイレクション(>,<)文字などコマンドを分割する文字を探すときに、二重引用符で囲われた部分については対象外とします。
最初のコマンドは&文字の前に奇数個の二重引用符があるので、囲われた中であると解釈され、&で区切られません。
次に、区切られないままコマンドが実行されるのですが、コマンドへの引数の渡され方です。Windowsではプロセスに引数は単一の文字列として渡されます。空白文字で区切ったりするのはプロセス側の仕事とされています(といっても、プログラマが区切り処理を書くことは滅多になく、処理系のスタートアップコードに記述されていることがほとんどです。だから、Cならargv, argcの配列に区切られて渡されてきたかのように扱えます)。
区切られないまま子プロセスのcmd.exeに渡される引数は、こう。
/c "set aaa=" bbb "&set aaa"
cmd.exeはこれを頭から読んでいって、スイッチでない文字列が登場したところから最後までをコマンド入力と解釈します。このとき、そのコマンド入力の先頭と末尾が二重引用符で囲われているとはずしてしまいます。この二重引用符外しが結果として入れ子解釈となりました。
set aaa=" bbb "&set aaa