LoginSignup
6

More than 1 year has passed since last update.

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

Posted at

この記事は、もともとQrunchというサービスに掲載していた記事ですが、サービスごとなくなっていました。今回、ググって解決しづらかったこと Advent Calendarのテーマにぴったりだと感じたので11日目の記事として再掲致します。

以下、公開当時の内容をそのまま載せます。

ここ数年開発時は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めんどくさいね!🏁

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6