CommandLineToArgvW()でのコマンドライン解析について

[戻る]
なつ 2013/06/08(土) 14:12:25
環境:VS2010、MFC

exeに渡されたコマンドラインを表題のAPIで解析しようと思っていますが、
文字列の最後に'\'が付いていると正常に読み取れません。
エスケープシーケンスとして扱われてしまい、次のパラメータとくっついてしまいま
す。
何か良い回避方法は無いでしょうか?


・コマンド
aaa.exe "AAA" "C:\BBB\" "CCC"

・コマンド解析結果
1番目:AAA
2番目:C:\BBB" CCC
※2番目が「C:\BBB\" CCC」になっていたかもしれません

・期待値
1番目:AAA
2番目:C:\BBB\
3番目:CCC


コマンドライン解析部分の
ソースは以下のように書いています。

TCHAR **pArgs = NULL;
int nArgs = 0;

pArgs = CommandLineToArgvW( GetCommandLine(), &nArgs );

YuO [E-Mail] 2013/06/08(土) 23:31:09
バックスラッシュに連なる二重引用符は特別扱いするのがCommandLineToArgvWの規則です。
VC++のargvも,同一の規則が適用されるようです。

[MSDN] CommandLineToArgvW function
http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391.aspx
[MSDN] Parsing C++ Command-Line Arguments
http://msdn.microsoft.com/en-us/library/windows/desktop/17w5ykft.aspx

なので,自分で解析するか,\を\\に置換してからCommandLineToArgvWを呼び出すとよいで
しょう。

forty-five 2013/06/09(日) 10:25:41
::PathGetArgs() という API もありますよ。

void main()
{
    TCHAR buffer[1024] = _T("aaa.exe \"AAA\" \"C:\\B BB\\\" \"CCC\"");

    LPTSTR p = buffer;

    while (*p)
    {
        _RPT1(_CRT_WARN, "%s\n", p);

        p = ::PathGetArgs(p);
    }
}

出力:
aaa.exe "AAA" "C:\B BB\" "CCC"
"AAA" "C:\B BB\" "CCC"
"C:\B BB\" "CCC"
"CCC"

なつ 2013/06/10(月) 23:59:37
>YuOさん
\を\\に置き換えると、"前以外の\も置き換わってしまって判別が難しそうです。
"の前のみ置き換えない、という方法にすると文字列としての"が
認識できなくなってしまい、両立に悩んでいます。

"AAA" "C:\BBB\" "CCC.exe" "-D \"param\""

このようなパラメータを上手く判別する方法が思いつかず悩んでいます。


>forty-fiveさん「
このPathGetArgs()というAPIで実現できそうだったのですが、
文字列としての"を認識してくれないようです。

また、MSDNなどに記述がありましたが、
渡せる文字列に260バイトという制限があるのでしょうか?
自分で試した限りでは520バイト以上でも渡せたので、
それを良しとして良いのかどうか・・・
(自分の英語の読解が間違ってたらすみません)

参考:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773602(v=vs.85).aspx

forty-five 2013/06/11(火) 20:47:26
エスケープさせたいのかさせたくないのか明確にした方が良いと思います。

"AAA" "C:\BBB\" "CCC.exe" "-D \"param\""

この例だと最初の \" はエスケープさせたくないんですよね?
そして、2個目と3個目の \" はエスケープさせたいということなんですよね?

void PathUnescapeQuotes(LPTSTR string)
{
    LPTSTR src = string;
    LPTSTR dst = string;

    while (true)
    {
        if (src[0] == _T('\\') && src[1] == _T('\"'))
        {
            src++; // skip _T('\\')
        }

        *dst = *src;

        if (*src == _T('\0'))
            break;

        src++;
        dst++;
    }
}

void main()
{
    TCHAR buffer[1024] = _T("\"AAA\" \"C:\\BBB\\\" \"CCC.exe\" \"-D \\\"param\\
\"\"");

    LPTSTR p = buffer;

    while (*p)
    {
        TCHAR string[1024];
        ::StrCpy(string, p);
        ::PathRemoveArgs(string);
        ::PathUnquoteSpaces(string);
        ::PathUnescapeQuotes(string);

        _RPT1(_CRT_WARN, "%s\n", string);

        p = ::PathGetArgs(p);
    }
}

出力:
AAA
C:\BBB\
CCC.exe
-D "param"

"C:\BBB\" の \" は最初と最後に " が付いているのでエスケープしていません。
"-D \"param\"" の内部の \" はエスケープしています。

なつ 2013/06/13(木) 23:04:18
[[解決]]
>forty-fiveさん
すいません、書き方が悪くてわかりにくかったですね。
おっしゃる通り、以下の期待値を得たかったのです。

"AAA" "C:\BBB\" "CCC.exe" "-D \"param\""

1番目:AAA
2番目:C:\BBB\
3番目:CCC.exe
4番目:-D "param"

※ "〜"で括られた中の場合のみ、"を文字列として扱いたいと考えていました。

提示して頂いた内容で、希望の処理が出来そうです。
ありがとうございます。


ただ、以下のような\"が"〜"の中に一つだけのパターンだと
正常に取得出来ないようです。
※PathRemoveArgs や PathGetArgs で上手く認識出来てないみたいです。

"\"AAA" "BBB"   :NG ⇒くっついてしまう
"AAA\"" "BBB"   :NG ⇒くっついてしまう
"\"AAA\"" "BBB" :OK ⇒"AAA"、BBBとして取れる



ただ、"〜"の中に\"が一つだけのパターンはまず無いと思っていますので、
今回は目を瞑ることにしました。

もし、どなたか回避方法がわかる方がいらっしゃったら教えて頂けると幸いです。

ITO 2013/06/14(金) 18:42:31
>もし、どなたか回避方法がわかる方がいらっしゃったら教えて頂けると幸いです。
ないと思います。(例外があれば別です)
バックスラッシュと¥の区別のための記号なのでないと思います。
後はどうしてもといわれれば、アスキーコードで判断するぐらいですね。

forty-five 2013/06/14(金) 23:27:26
根本的に仕様を変えられるのなら、

HTML 方式

bool replace(LPTSTR& src, LPTSTR& dst, LPCTSTR srcPattern, LPCTSTR dstPattern)
{
    if (_tcsncmp(src, srcPattern, _tcslen(srcPattern)) == 0)
    {
        _tcscpy(dst, dstPattern);

        src += _tcslen(srcPattern);
        dst += _tcslen(dstPattern);

        return true;
    }

    return false;
}

void HtmlUnescape(LPTSTR string)
{
    LPTSTR src = string;
    LPTSTR dst = string;

    while (true)
    {
        if (!replace(src, dst, _T("""), _T("\"")) &&
            !replace(src, dst, _T("&"), _T("&")) &&
            !replace(src, dst, _T("&lt;"), _T("<")) &&
            !replace(src, dst, _T("&gt;"), _T(">")) &&
            !replace(src, dst, _T("&nbsp;"), _T(" ")))
        {
            *dst = *src;

            if (*src == _T('\0'))
                break;

#ifndef UNICODE

            if (::IsDBCSLeadByte(*src))
            {
                src++;
                dst++;

                *dst = *src;
            }

#endif

            src++;
            dst++;
        }
    }
}

void main()
{
    TCHAR seps[1024] = _T(" ");
    TCHAR buffer[1024] = _T("空白&nbsp;のテスト 二重引用符&quot;のテスト");

    LPTSTR token = _tcstok(buffer, seps);

    while (token)
    {
        TCHAR string[1024];
        _tcscpy(string, token);
        ::HtmlUnescape(string);

        _RPT1(_CRT_WARN, "%s\n", string);

        token = _tcstok(0, seps);
    }
}

URL 方式

void main()
{
    TCHAR seps[1024] = _T(" ");
    TCHAR buffer[1024] = _T("空白%20のテスト 二重引用符%22のテスト タブ%09のテス
ト 改行%0Aのテスト");

    LPTSTR token = _tcstok(buffer, seps);

    while (token)
    {
        TCHAR string[1024];
        _tcscpy(string, token);
        DWORD c = 1024;
        ::UrlUnescape(string, 0, &c, URL_UNESCAPE_INPLACE);

        _RPT1(_CRT_WARN, "%s\n", string);

        token = _tcstok(0, seps);
    }
}

URL 方式ならタブやら改行やら何でも渡せます。

古いスレッドには追加発言できません。