BETA

Windowsのコマンドライン引数でのクォートの話

投稿日:2020-05-22
最終更新:2020-05-23

ここ数年開発時はPowerShellを使っていて、ずっと困っていたことがありました。

例えばgit commit-mオプションにダブルクォートを渡したくなったとき、PowerShellではエスケープシーケンスにバッククォートを使うとのことなので👇のように書いてみたとします:

> git commit -m "Implement `"Hello, world`" finally!"  
error: pathspec 'world finally!' did not match any file(s) known to git  

なぜかダブルクォートが適切にエスケープされていないかのようなエラーになってしまいました。

シングルクォートで囲えばいいだろ、と思ってやってみてもやっぱりダメ:

> git commit -m 'Implement "Hello, world" finally!'  
error: pathspec 'world finally!' did not match any file(s) known to git  

おかしいなぁと悩みながら、なんとなくバッククォートの前にバックスラッシュを付けてみたところ、なんとうまくいくじゃありませんか!

> git commit -m "Implement \`"Hello, world\`" finally!"  
On branch master  
Your branch is up to date with 'origin/master'.  

...  

なぜだろうと首をひねっていたところ、Twitterでこんな⬇️情報を教えていただきました:

必要なところをかいつまんで説明しましょう。

Windowsにおいてコマンドを呼び出す最もプリミティブなAPI、--- つまり、Windowsで子プロセスを作るあらゆるアプリケーションが間接的に使うAPI ---、CreateProcessでは、コマンドライン引数は、文字列の配列ではなく一つの文字列として渡されるそうなのです。
しかし、それでは普通のC言語のアプリケーションを書いたときmain関数のargvが常に2つの文字列(1つめはコマンドの名前ですね)になってしまって不便なので、呼び出されたコマンドがmain関数に渡す前に)自らコマンドライン引数をパースしているというのです!

その際どのようにコマンドライン引数をパースするかは、使用したプログラミング言語、特にCやC++ではVC++ランタイムのバージョンによって異なるそうです😵!

冒頭で紹介したダブルクォートに関するルールも、上記のページにもっと詳しく書かれています。
つまりバッククォートだけでなくバックスラッシュを付ける必要があったのは、PowerShellの仕様ではなく、Windowsで動くコマンド全般に関する挙動だったんですね!
どうりでPowerShellについて解説したページには出てこないワケです。

そして、今回は試してませんが、きっとコマンドプロンプトでも同じ問題にぶち当たるのでしょう。

Windowsではコマンドライン引数はアプリケーションがパースする、という話はどこかで聞き覚えがありましたが、ダブルクォートとかも自前で処理してたんですね😰...

ちなみに、実は同じ原因の問題は、stack test--test-argumentsを使うときにもしばしばぶち当たっていました。
例えば、Hspecで書いたテストのうち、テストの説明にfoo barという(空白を含む)文字列を含むものを実行したいとき:

> stack test --fast --test-arguments "--match `"foo bar`""  
Error: While constructing the build plan, the following exceptions were encountered:  

Unknown package: bar  

Some different approaches to resolving this:  


Plan construction failed.  

これもgit commitの場合と同様、バッククォートの前にバックスラッシュを付ければ回避できます。

> stack test --fast --test-arguments "--match \`"foo bar\`""  

もちろん、ダブルクォートの文字列リテラルの代わりに、シングルクォートの文字列リテラルでダブルクォートの前にバックスラッシュを付けるのでも🆗です:

> stack test --fast --test-arguments '--match \"foo bar\"'  

これで完璧!🙌
Windowsめんどくさいね!🏁

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@igrepの技術ブログ。https://the.igreque.info/のサブとして運用しよう

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう