Windows と Unix(macOS,Linuxなど) の違い
Unix ではプロセスを起動するとき、コマンドラインとして「文字列の一覧」を渡すので、それが main 関数の引数になります。
Windows ではプロセスを起動するとき、コマンドラインは「文字列が1つ」なので、呼び出された側が、この文字列を解析して main 関数の引数にします。よって、main 関数の引数はアプリケーション側の解析方法次第になります。
標準ライブラリの動作
GetCommandLine 関数で取得できるコマンドライン文字列が解析されます。処理内容は
- 空白文字で分割される
- 空白文字を引数に含めるには「"」で括る必要がある
- 「"」から「"」までの括りを1つの引数とする
- 途中で2つ並んだ「"」は、1文字の「"」になる
- 最初と最後の「"」は削除される
- 「 \ 」の直後に「"」がないとき
- 「 \ 」は文字として扱われる
- 「 \ 」の直後に「"」があるとき
- 引数では、連続した「 \ 」の数が半分になる
- 連続した「 \ 」の数が偶数のときは「"」は括り文字になる
- 連続した「 \ 」の数が奇数のときは「"」を文字として扱う
となるようです。「 %env% 」形式の環境変数展開処理は行われません。
パス名の前後を「"」で括って、パス区切りを「 \ 」で渡すとき要注意です。
以下、テスト プログラムで動作確認します。
動作確認
VisualStudio 2019 : C++ コンソール アプリのプロジェクトを使います。
#include <stdio.h>
extern "C" __declspec(dllimport) char * __stdcall GetCommandLineA(void);
#define GetCommandLine GetCommandLineA
int main(int argc, char **argv)
{
printf("GetCommandLine: 「%s」\n", GetCommandLine());
for (int n = 0; n < argc; n++)
printf("argv[%d]: 「%s」\n", n, argv[n]);
return 0;
}
文字列を「…」で括るようにして出力します。
コマンド ライン文字列の分割
文字列の途中にある空白で分割されます。
> print_argv 1 2 3
GetCommandLine: 「print_argv 1 2 3」
argv[0]: 「print_argv」
argv[1]: 「1」
argv[2]: 「2」
argv[3]: 「3」
連続空白でも同じ結果になります。
> print_argv 1 2 3
GetCommandLine: 「print_argv 1 2 3」
argv[0]: 「print_argv」
argv[1]: 「1」
argv[2]: 「2」
argv[3]: 「3」
引用符としてのダブルクォート「"」
ダブルクォート「"」で始まり、ダブルクォート「"」で終わる範囲を一連の文字列とし、先頭と最後のダブルクォート「"」は削除されます。途中にある空白文字は引数分割文字ではなくなります。
> print_argv "1 2 3"
GetCommandLine: 「print_argv "1 2 3"」
argv[0]: 「print_argv」
argv[1]: 「1 2 3」
途中にダブルクォート「"」が2つ並んでいると、ひとつのダブルクォート「"」文字とみなされます。
> print_argv "1""2 3"
GetCommandLine: 「print_argv "1""2 3"」
argv[0]: 「print_argv」
argv[1]: 「1"2 3」
バックスラッシュ「\」
バックスラッシュ「\」は、パスの区切り文字なので、そのまま使いたい場合と、エスケープ文字の役割をさせたい場合があるので厄介です。
連続したバックスラッシュ「\」の直後がダブルクォート「"」でない場合
エスケープ文字として機能せず、文字として扱われます。
> print_argv a\b
GetCommandLine: 「print_argv a\b」
argv[0]: 「print_argv」
argv[1]: 「a\b」
> print_argv a\\b
GetCommandLine: 「print_argv a\\b」
argv[0]: 「print_argv」
argv[1]: 「a\\b」
> print_argv a\ b
GetCommandLine: 「print_argv a\ b」
argv[0]: 「print_argv」
argv[1]: 「a\」
argv[2]: 「b」
バックスラッシュ「\」の直後がダブルクォート「"」の場合
まず、引数に渡るバックスラッシュ「\」の数が元の半分にされます。
- 連続するバックスラッシュ「\」の数が奇数のときは、ダブルクォート「"」が追加されます。
> print_argv a\"b
GetCommandLine: 「print_argv a\"b」
argv[0]: 「print_argv」
argv[1]: 「a"b」
> print_argv a\\"b
GetCommandLine: 「print_argv a\\"b」
argv[0]: 「print_argv」
argv[1]: 「a\b」
> print_argv a\\\"b
GetCommandLine: 「print_argv a\\\"b」
argv[0]: 「print_argv」
argv[1]: 「a\"b」
> print_argv a\\\\"b
GetCommandLine: 「print_argv a\\\\"b」
argv[0]: 「print_argv」
argv[1]: 「a\\b」
- 連続するバックスラッシュ「\」の数が偶数のときの、ダブルクォート「"」は引用符になります。
> print_argv a\"b c"
GetCommandLine: 「print_argv a\"b c"」
argv[0]: 「print_argv」
argv[1]: 「a"b」
argv[2]: 「c」
> print_argv a\\"b c
GetCommandLine: 「print_argv a\\"b c」
argv[0]: 「print_argv」
argv[1]: 「a\b c」
> print_argv a\\\"b c"
GetCommandLine: 「print_argv a\\\"b c"」
argv[0]: 「print_argv」
argv[1]: 「a\"b」
argv[2]: 「c」
> print_argv a\\\\"b c"
GetCommandLine: 「print_argv a\\\\"b c"」
argv[0]: 「print_argv」
argv[1]: 「a\\b c」
コマンド プロンプトによる環境変数の展開
コマンド プロンプトは「%env%」形式の文字列が展開されます。
> echo %windir%
C:\WINDOWS
> print_argv %windir%
GetCommandLine: 「print_argv C:\WINDOWS」
argv[0]: 「print_argv」
argv[1]: 「C:\WINDOWS」
> print_argv %^windir%
GetCommandLine: 「print_argv %windir%」
argv[0]: 「print_argv」
argv[1]: 「%windir%」
プロセスに渡された時点で、既に展開されています。呼び出された側では展開していません。
system 関数ではシェル(cmd.exe)を経由するので、環境変数展開が実行されますが、CreateProcess 関数では cmd.exe を使わないので動作が異なります。