「bogus」をエミュレートしたければ皿まで (shell32.CommandLineToArgvW)

「これは解にならない」と結論付けられていた回答がワタシには解になった、てオハナシ。

ここにあるこれ:

1 def win_CommandLineToArgvW(cmd):
2     import ctypes
3     nargs = ctypes.c_int()
4     ctypes.windll.shell32.CommandLineToArgvW.restype = ctypes.POINTER(ctypes.c_wchar_p)
5     lpargs = ctypes.windll.shell32.CommandLineToArgvW(unicode(cmd), ctypes.byref(nargs))
6     args = [lpargs[i] for i in range(nargs.value)]
7     if ctypes.windll.kernel32.LocalFree(lpargs):
8         raise AssertionError
9     return args

Python 3 で NG なのはともかくとして。

この回答者は、「こんなだから解決にならん」と言っている:

1 >>> win_CommandLineToArgvW('aaa"bbb""" ccc')
2 [u'aaa"bbb"""', u'ccc']
3 >>> win_CommandLineToArgvW('""  aaa"bbb""" ccc')
4 [u'', u'aaabbb" ccc']

これは、「今起動している python.exe から見た自分に渡ってきたコマンドラインを解読する(要は sys.argv)」ということなら、「これはダメダ」は正解。

ところがそうではなく「DOS コマンドラインであることが期待されている文字列を argv リストに分解する」ということであるならば、これは完全に解になる。やったね。

こんな定義ファイルがあったとする:

1 <ExecteCmd>
2 <![CDATA[
3 @ECHO OFF
4 COPY "i have dquotes("") and macro reference($(Xxx))" somedir\
5 ]]>
6 </ExecteCmd>

これを処理するプロセッサとしては「subprocess で cmd.exe に垂れ流せない」、つまり cmd.exe が知らないマクロ参照「$(Xxx)」を含むためにこれを予め展開しなければならないわけなのだが、困るのは上みたいな「すげー大変」そうにみえるものよりは実際はこっち:

1 <ExecteCmd>
2 <![CDATA[
3 @ECHO OFF
4 COPY $(TargetFile) $(SolutionDir)
5 ]]>
6 </ExecteCmd>

なんの問題も起こさないようにみえるかもしれないが、参照を剥がしたら空白が呼ばれて飛び出たり引用符が含まれたりするので、まさに「見かけから想像がつかない複雑さを持つ」ハメになる。だってこの場合引用符を勝手に付与出来ないでしょ?:

1 <ExecteCmd>
2 <![CDATA[
3 @ECHO OFF
4 COPY $(TargetFile) "$(SolutionDir)\Release Unicode"
5 ]]>
6 </ExecteCmd>

つまり今の場合、「おバカで名を馳せる cmd.exe」がそうするのと「完全に同じように argv リストに分解する」手段が必要なのだ。

さらにもう一段、「そのおバカに付き合う呼び出される側プログラム」のバカさも同時に完全エミュレートする必要がある。上の「こりゃダメダ」とされている例からわかる通り、「二重引用符で囲まれた文字列」は「そのまま」渡ってくるので、これをひっぺがすのは「呼ばれた側の責務である」という皿まで喰らう必要がある。上の「"$(SolutionDir)\Release Unicode"」がそれで、この場合「マクロ参照はがし君」は「既に引用符で囲まれているや否や」によって処理を分ける必要がある。というわけで、かえって「馬鹿に完全互換」であることが非常に幸いする、てわけ。

伝わるかなぁ…?

ちなみにワタシにニーズとしては CommandLineToArgvW に加えて継続行の扱いだけあればほとんど完全に「オレ的に完璧に用を足せ」たりする。

「DOS パーサ」といった完全なものはワタシには用はないけれど、それにかなり匹敵するような「日常にはほとんど問題ないパーサ(に近いもの)」を書けそうな気がしてきたので、ある程度整理できたらお見せしようと思う。(ただし CommandLineToArgvW 前提になるので Windows 版の CPython 公式以外では使えないヤツね、残念ながら。)