ctypes.windll.shell32.ShellExecute例

いつものように自分だけのためのメモである。

とにかく毎日が CUI と GUI との間の頻繫な行き来なので、特に bash と emacs からの「なにかしらの起動」が結構鬱陶しい。普段は「cmd.exe」の「start」でどうにかごまかしていたが、日本語を含んだりすると途端に破綻したり、引用符の関係がぶっ壊れてたりで、まぁ「ないよりはマシ」でしかなかった。うーん、「start」って ShellExecute してくれてるだけだよなぁ、と、いまさら:

2020-10-24追記: 最初に挙げた版からちょっとだけ変えた。(拡張子補ってる部分。)
 1 #! py -3
 2 # -*- coding: utf-8 -*-
 3 import ctypes
 4 import sys
 5 import os
 6 
 7 
 8 if __name__ == '__main__':
 9     import argparse
10     parser = argparse.ArgumentParser()
11     parser.add_argument("command")
12     parser.add_argument("--cmdargs")
13     parser.add_argument(
14         "--verb",
15         choices=["edit", "find", "open", "print", "properties", "runas"],
16         default="open")
17     nShowCmd = (
18         ("SW_HIDE", 0),
19         ("SW_SHOWNORMAL", 1),
20         ("SW_SHOWMINIMIZED", 2),
21         ("SW_SHOWMAXIMIZED", 3),
22         ("SW_MAXIMIZE", 3),
23         ("SW_SHOWNOACTIVATE", 4),
24         ("SW_SHOW", 5),
25         ("SW_MINIMIZE", 6),
26         ("SW_SHOWMINNOACTIVE", 7),
27         ("SW_SHOWNA", 8),
28         ("SW_RESTORE", 9),
29         ("SW_SHOWDEFAULT", 10),
30     )
31     parser.add_argument(
32         "--showcmd",
33         choices=[m for m, v in nShowCmd],
34         default="SW_SHOWNORMAL")
35     args = parser.parse_args(sys.argv[1:])
36     cmd = os.path.normpath(args.command)
37     pth = os.environ["PATH"].split(";")
38     while not os.path.exists(cmd) and pth:
39         p = pth.pop(0)
40         cmd = os.path.normpath(os.path.join(p, args.command))
41         for ext in ("", ".exe",):
42             if os.path.exists(cmd + ext):
43                 cmd += ext
44                 break
45     if not os.path.exists(cmd):
46         raise OSError("{} was not found.".format(args.command))
47     ctypes.windll.shell32.ShellExecuteW(
48         0,
49         args.verb,
50         cmd,
51         args.cmdargs,
52         "",
53         dict(nShowCmd)[args.showcmd])

Windows 専用なので shebang が「py」なのは問題なかろ。あと PATH の展開はいわゆる「余計なお世話」かもしらん。真似したい場合、必要ないならそこは消して使っておくれ。それと verb は全然期待した動きしてないんだが…、こんなんだったっけ? win9x 時代に使ってた記憶だと、機能してたハズなんだが…。まぁいいや、ワタシは気にしない。


2021-04-09追記:
ShellExecuteを使う上記アプローチは日常で概ねいいんだけど、「DOS な start の方が楽」なケースもあることに気付いた。

ワタシはよくローカルな html ファイルを作るのだけれど、当然これは「ブラウザで開きたい」。ならば当然ながら「今開いてるブラウザ(ワタシの場合は Chrome)のタブとして開きたい」わけね。もちろん、生成した html がある場所をエクスプローラで開いて、Chrome 側には新規タブを開いて、その新規タブにドロップ、という手順を取れば目的のことが出来るわけで、「めんどうくさい」だけ、これは。けどそう、「めんどうくさい」ので、作業状態によっては「コマンドラインから開きたい」。

実際「コマンドラインから」の楽さというのは bash などの優秀なシェル機能の「補完」あってこそだが、とにかくそれに頼れるならラクチンだ、ってことなのだけれど、上記のスクリプトだと以下の代替が簡単には出来ない:

MSYS コマンドラインから。
1 [me@host: ~]$ cmd /c "start chrome -new-tab file://`pwd -W`/my_generated.html"

まずそもそもが「c:/Program Files (x86)/Google/Chrome/Application/」にパスを通しているユーザなんか普通は皆無だろうから、だからこそ「start」と「ShellExecute」が活きる。これらどちらかに依存しない場合、「パスを通す」か「フルパスで起動する」かするしかなく、「やぁねぇ」である。そして、まぁ chrome に与えるべき引数だよね、これがワタシが上で書いたスクリプトからそもそも与えるのがめんどい。

てわけで、「start と使い分けよう」てことだね、結局。


2021-04-10追記:
chrome -newtab 問題に関しては、結構使うので専用のを作っちゃった方が楽だね、てことで:

chrometabopen_with_localfile
 1 #! py -3
 2 # -*- coding: utf-8 -*-
 3 from __future__ import unicode_literals
 4 
 5 import ctypes
 6 import sys
 7 import os
 8 #import pipes
 9 
10 
11 if __name__ == '__main__':
12     import argparse
13     parser = argparse.ArgumentParser()
14     parser.add_argument("localfile")
15     args = parser.parse_args(sys.argv[1:])
16     p = os.path.abspath(args.localfile).replace("\\", "/")
17     ctypes.windll.shell32.ShellExecuteW(
18         0,
19         "open",
20         "chrome",
21         '-newtab "file://{}"'.format(p),
22         "",
23         1)  # 1: SW_SHOWNORMAL

ファイル名に引用符を含む場合におかしくなる気がするんだけど、どう措置していいもんかわからん。そう、いつもの Windows の闇。


2021-04-27追記:
「chrome -newtab ザク」について、そこそこの機能拡張して gist た:

gist に貼り付ける直前に Tools/scripts/google.py とその前提となる webbrowser.py の存在を思い出して、慌ててコメントを書いたりもしましたが私は元気ですか?