search
LoginSignup
5
Help us understand the problem. What are the problem?

ググって解決しづらかったこと Advent Calendar 2021 Day 11

posted at

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

この記事は、もともと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
What you can do with signing up
5
Help us understand the problem. What are the problem?