1
0

Windows のコマンド ライン引数処理

Last updated at Posted at 2020-12-15

Windows と Unix(macOS,Linuxなど) の違い

Unix ではプロセスを起動するとき、コマンドラインとして「文字列の一覧」を渡すので、それが main 関数の引数になります。

Windows ではプロセスを起動するとき、コマンドラインは「文字列が1つ」なので、呼び出された側が、この文字列を解析して main 関数の引数にします。よって、main 関数の引数はアプリケーション側の解析方法次第になります。

標準ライブラリの動作

GetCommandLine 関数で取得できるコマンドライン文字列が解析されます。処理内容は

  • 空白文字で分割される
    • 空白文字を引数に含めるには「"」で括る必要がある
  • 「"」から「"」までの括りを1つの引数とする
    • 途中で2つ並んだ「"」は、1文字の「"」になる
    • 最初と最後の「"」は削除される
  • 「 \ 」の直後に「"」がないとき
    • 「 \ 」は文字として扱われる
  • 「 \ 」の直後に「"」があるとき
    • 引数では、連続した「 \ 」の数が半分になる
    • 連続した「 \ 」の数が偶数のときは「"」は括り文字になる
    • 連続した「 \ 」の数が奇数のときは「"」を文字として扱う

となるようです。「 %env% 」形式の環境変数展開処理は行われません。

パス名の前後を「"」で括って、パス区切りを「 \ 」で渡すとき要注意です。


以下、テスト プログラムで動作確認します。

動作確認

VisualStudio 2019 : C++ コンソール アプリのプロジェクトを使います。

print_argv.cpp
#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 を使わないので動作が異なります。

1
0
0

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
1
0