雑に作ったPythonスクリプトを雑に使う(Windows)
TL;DR
- Windowsで
hogehoge.py
を適当な場所に保存した後、任意のカレントディレクトリからこれを> hogehoge
で呼べるようにしたい。 - 言い換えると、以下のような真似ができるようになる
deploy.exe
が欲しい。
C:\some\dir> echo print("hoge") > hogehoge.py C:\some\dir> deploy hogehoge.py C:\some\dir> cd C:\another\dir C:\another\dir> hogehoge hoge
- ……ので作りました。
- でも力技気味なのでもう少しスマートな方法があったら教えてください。
- →ありました(コメント欄)
本編
経緯
簡単なツールを雑に作るとき、スクリプト言語は大層便利です。
具体的には、LoL*1で内戦*2をするときにサクッとチーム決めをしたいなら、Pythonの出番です:
import sys import os.path import random members = { "a": "A-man", "b": "B-man", "c": "C-man", "d": "D-man", "e": "D-man", } def team_gacha(s): a = list(map(lambda c: members[c], s)) random.shuffle(a) return a[:len(a) // 2], a[len(a) // 2:] if __name__ == "__main__": if len(sys.argv) > 1: print(team_gacha(sys.argv[1])) else: print(f"Usage: {os.path.basename(__file__)} <member-string>") print() print("Following chars are available in <member-string>:") for k, v in members.items(): print(f"\t{k}: {v}")
これをteam_gacha.py
で保存すれば……
> python team_gacha.py abcd ['A-man', 'C-man'] ['B-man', 'D-man']
簡単です!これでいつでもLOLで内戦ができます!
……と、コードまで貼り付けておいて何ですが、そんな話はどうでもいいわけです。 重要なのはこの後です。
さっきのように最高の便利スクリプトを作ったとして、これをいつまでもデスクトップに置いておくわけにはいきません。邪魔なので。
すると当然適当なディレクトリにお引越しをするわけです。
では、引越し後のteam_gacha.py
を適当なカレントディレクトリから実行するためにはどうすればよいでしょう?
答えはこうです:
> python C:\my\tools\dir\team_gacha.py abcd
あれ???面倒では???
ここで1つ名案があります。引越し先のC:\my\tools\dir
にパスを通せばいいのです。
しかし残念でした。適当なカレントディレクトリから不思議な力で呼び出せるのはパスの通ったディレクトリにある.exe
と.bat
だけです*3。
「じゃあteam_gacha.py
を呼ぶコマンドを.bat
に書いてパスの通った場所に置けばいいのでは?」と思ったあなたは疑いようもなく正しいです。
でも僕は> team_gacha.bat
とすら打ちたくないんです。やだーーー!!!> team_gacha
だけで実行したいーーーー!!!
この七面倒くさい客*4を黙らせるにはteam_gacha.py
を呼ぶ.exe
を作るしかありません!
"コマンドを叩くexe"を生成するexeを作る
以下、解決編です。目新しいことは特にやってないので、ソースコード斜め読みでも十分な気がする。
さて、先程の客*5の要求を一般化すると「適当なコマンドを叩く.exe
が欲しい」ということになります。要は、本来は.bat
でやるようなことを.exe
でやろうというのです。
実際のところそれ自体はなんら難しいことではありません。C言語のstdlib.h
にはsystem()
というコマンドを叩くための関数があるからです。しかし、.bat
を書く感覚でいちいちC書きたいかっていうとそんなわけはないわけで……。つまり何が言いたいかというと、「適当なコマンドを叩く.exe
」を簡単に作るツールが欲しいのです。欲を言えばそのツールも簡単に、つまり> hogehoge
のように呼び出せたら嬉しいので、ツールは.exe
の姿をしていることが好ましいです。
そんなことできるのか……と一瞬思いましたが、何のことはなく、実行時にソースコードを生成してビルドまでやってしまえばいいだけです。特にひねりも何もないのでソースだけ乗っけて説明は省きます。こちらがシェフの気まぐれソースのソース詰めでございます。
C言語を大学の講義以外で書いたの初めてでは……?(過言)
#include <stdio.h> #include <stdlib.h> #include <string.h> #define BUF_SIZE 1024 #define CNAME_SIZE 256 int main(int argc, char const *argv[]) { if (argc < 3) { printf("Usage: cm2exe <command-name> <command>\n"); return EXIT_SUCCESS; } // 第1引数: 生成するexeの名前 const char* cname = argv[1]; // 第2引数: 生成するexeが実行するコマンド const char* command = argv[2]; if (strlen(cname) > CNAME_SIZE) { printf("CM2EXE ERROR: Command name length must be less than %d.\n", CNAME_SIZE); return EXIT_FAILURE; } // 一時的な作業ディレクトリを生成 if (system("mkdir temp")) { printf("CM2EXE ERROR: \"temp\" directory already exists.\n"); return EXIT_FAILURE; } // 一時的なソースコードを生成 FILE* source_code = fopen("temp/source_code.c", "w"); if (source_code == NULL) { printf("CM2EXE ERROR: cannot create \"temp/source_code.c\".\n"); return EXIT_FAILURE; } fprintf(source_code, "#include <stdio.h>\n" "#include <stdlib.h>\n" "#include <string.h>\n" "#define BUF_SIZE %d\n" "int main(int argc, char const *argv[]) {" "char c[BUF_SIZE] = {};" "int len = 0;" "strcat(c, \"%s\");" "len += strlen(c);" "if (len > BUF_SIZE) {" "printf(\"CM2EXE ERROR: Command length must be less than %%d\", BUF_SIZE);" "return 1;" "}" "for (int i = 1; i < argc; i++) {" "len += 1 + strlen(argv[i]);" "if (len > BUF_SIZE) {" "printf(\"CM2EXE ERROR: Command length must be less than %%d\", BUF_SIZE);" "return 1;" "}" "strcat(c, \" \");" "strcat(c, argv[i]);" "}" "system(c);" "return 0;" "}", BUF_SIZE, command); fclose(source_code); // ビルド char buf[CNAME_SIZE + 32]; sprintf(buf, "gcc temp/source_code.c -o %s", cname); system(buf); // 一時ディレクトリと一時ファイルを消去 system("rd temp /S /Q"); return EXIT_SUCCESS; }
ちなみにソースの中にあるソースはこんな感じ:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define BUF_SIZE 1024 int main(int argc, char const *argv[]) { char c[BUF_SIZE] = {}; int len = 0; strcat(c, "%s"); // %s にはコマンドが入る len += strlen(c); if (len > BUF_SIZE) { printf("CM2EXE ERROR: Command length must be less than %d", BUF_SIZE); return 1; } for (int i = 1; i < argc; i++) { len += 1 + strlen(argv[i]); if (len > BUF_SIZE) { printf("CM2EXE ERROR: Command length must be less than %d", BUF_SIZE); return 1; } strcat(c, " "); strcat(c, argv[i]); } system(c); return 0; }
これをビルドしたものをcm2exe.exe
として、> cm2exe hogehoge hugahuga
とすれば、hugahuga
を叩くだけのhogehoge.exe
が出来上がるというわけです。
ただ、以下には十分注意すべきです:
「"コマンドを叩くexe"を生成するexe」を自動で叩くexeを生成する
これからは新しく便利なhogehoge.py
を書いたら
> move hogehoge.py C:\my\tools\dir > cm2exe hogehoge "python C:\my\tools\dir\hogehoge.py" > move hogehoge.exe C:\my\tools\dir > hogehoge very cool output!!
するだけ!
……手数が多い。自動化します。今度はPythonで書きましょう。
from pathlib import Path import os import sys import shutil import subprocess BIN_DIR = r"exeを置いておく場所" LIB_DIR = r"スクリプトを置いておく場所" SCRIPT_CMD = { # 拡張子: 実行のためのコマンド ".py": "python", ".exs": "elixir", # 別にPythonに限らずとも使える } # ファイル名を拡張子とそれ以外に分割する def split_basename(path): basename = Path(str(path)).name tokens = str(basename).split(".") if len(tokens) < 2: return basename, None else: *base, ext = tokens return ".".join(base), "." + ext def deploy(script_path): # 実行コマンドを決定するために拡張子を確認 name = Path(script_path).name base, ext = split_basename(script_path) if ext is None: raise Exception(f"No extension in {script_path}.") if ext not in SCRIPT_CMD: raise Exception(f"Extension \".{ext}\" is not supported.") try: # スクリプトを所定の場所にコピー shutil.copy(script_path, LIB_DIR) # スクリプトを呼び出すexeを生成 subprocess.run([ "cm2exe", f"{BIN_DIR}/{base}", f"{SCRIPT_CMD[ext]} {LIB_DIR}/{name}" ]) except Exception as e: # clean up copied_script = Path(LIB_DIR) / name if copied_script.exists(): copied_script.unlink() deploied_bin = Path(BIN_DIR) / f"{base}.exe" if deploied_bin.exists(): deploied_bin.unlink() raise e if __name__ == "__main__": if len(sys.argv) > 1: try: deploy(sys.argv[1]) except Exception as e: print(f"Deploy Error: {e}") else: print(f"Usage: {Path(__file__).name} <script-path>")
こちらをdeploy.py
として、> python deploy.py deploy.py
で自分自身をデプロイします。すると今後は、冒頭の
> move hogehoge.py C:\my\tools\dir > cm2exe hogehoge "python C:\my\tools\dir\hogehoge.py" > move hogehoge.exe C:\my\tools\dir > hogehoge very cool output!!
が
> deploy hogehoge.py > hogehoge very cool output!!
で完結するようになりました。めでたしめでたし。
おわりに
絶対もっといい方法あるでしょ…………
追記
コメント欄にて素晴らしい方法を教えていただきました。eblblさんありがとうございました!