Pythonの便利なオプション-c, -m

Posted by rhoboro on 2021-03-07

小ネタです。
先日こんなツイートをしたので、関連してpythonコマンドの便利オプションである-cオプション、-mオプションを紹介します。 自分はよく使っているのですが、あまり活用されていない気がしているので。

Pythonに限らず似たようなことができる言語は多いですが、「欲しいコマンドは入ってないがPythonは入っている」はちょっと古いサーバー環境ではあるあるだと思います。 古いサーバーでなくても、Pythonアプリ用のDockerコンテナだとユーティリティ系のコマンドを入れていないことも多いですね。

Pythonの -c オプション

-cオプションは文字列で受け取ったコードを実行します。
改行の代わりには;を使いますが、インデントの利用が言語仕様で組み込まれているPythonでは複雑なことはできません。 それでもちょっとしたシーンではとても便利に使えます。

  • digやnslookupの代わりにIPアドレスを調べる
    • 先日Dockerコンテナ内でのネットワーク調査で利用
$ python3 -c 'import socket; print(socket.gethostbyname("www.rhoboro.com"))'
172.217.161.243
  • curlやwgetの代わりにファイルダウンロード
    • curlもwgetもない状況でのapt-getのGPGエラー時に公開鍵のダウンロードで利用
# 古いDebianだったのでPython2です
$ python -c 'from urllib import urlopen; open("some.key", "wb").write(urlopen("https://example.com/some.key").read())'
  • 上のPython3バージョン
$ python3 -c 'from urllib.request import urlopen; open("some.key", "wb").write(urlopen("https://example.com/some.key").read())'
  • md5の代わりにファイルの内容比較
    • サーバーAにあった鍵ファイルの中身をサーバーBにコピペしたときに利用
$ python3 -c 'from hashlib import md5; print(md5(open("some.key", "rb").read()).hexdigest())'
e178257ee2cab514ab5cb1831e4d0d95

Pythonの -m オプション

-mオプションはPythonのモジュールを実行します。
標準ライブラリでも-mで実行されることを想定したモジュールがいくつか存在しています。

  • venvモジュールで仮想環境の作成
# myvenvという名前で仮想環境を作成
$ python3 -m venv myvenv
  • httpモジュールで簡易webサーバーの起動
    • -d/--directoryでディレクトリの指定も可能
$ ls
__init__.py module.py
# デフォルトポートも8080
$ python3 -m http.server 8080
Serving HTTP on :: port 8080 (http://[::]:8080/) ...

http.server

  • jsonモジュールでJsonファイルの整形
    • --indentや--compact、--no-ensure-asciiなどのオプションがあります
$ cat mp_films.json | python3 -m json.tool --indent 2
[
  {
    "title": "And Now for Something Completely Different",
    "year": 1971
  },
  {
    "title": "Monty Python and the Holy Grail",
    "year": 1975
  }
]

$ cat mp_films.json | python3 -m json.tool --compact
[{"title":"And Now for Something Completely Different","year":1971},{"title":"Monty Python and the Holy Grail","year":1975}]
  • zipfileでzipアーカイブの作成、展開
    • zipコマンドやunzipコマンドがない時に便利
    • 展開時にパスワードがかかっている場合はRuntimeError
    • @shimizukawaさん、ありがとうございます
# ディレクトリを渡して作成
$ python3 -m zipfile -c pkg.zip pkg/

# ディレクトリに展開
$ python3 -m zipfile -e pkg.zip pkg2/
  • asyncioのイベントループ内で対話モードを起動
    • コルーチンをトップレベルで実行できます
    • 非同期処理を書いているときの動作確認でとても便利です
$ python3 -m asyncio
asyncio REPL 3.9.1 (v3.9.1:1e5d33e9b9, Dec  7 2020, 12:10:52)
[Clang 6.0 (clang-600.0.57)] on darwin
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> async def echo(s):
...   await asyncio.sleep(1)
...   print(s)
...
>>> await echo("Hello world")
Hello world

-mオプションとsys.path

「-mオプションはPythonのモジュールを実行します。」と書きましたが、python3コマンドでスクリプトを実行した時とは挙動が異なります。 例を見るのが早いので下記の pkg/module.py を用意します。

$ mkdir pkg
$ touch pkg/__init__.py
$ cat <<EOF > pkg/module.py                                                                                                                         [master:blog]
import sys


def main():
    print(f"{sys.path}")

if __name__ == "__main__":
    main()
EOF

まずは-mオプションなしで実行します。
この場合はスクリプトファイルを指定します。 sys.pathの先頭にはそのスクリプトファイルがあるディレクトリが入ります。
詳細については公式ドキュメントを確認してください。

$ pwd
/Users/rhoboro
$ python3 pkg/module.py
['/Users/rhoboro/pkg', '/Library/Frameworks/Python.framework/Versions/3.9/lib/python39.zip', '/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9', '/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages']

続いて、-mオプションをつけて実行します。
この場合はファイル名ではなく、モジュール名を指定します。 モジュールの探索はsys.path内を対象に行われますが、sys.pathの先頭にカレントディレクトリが入るため、ここではpkgパッケージも探索されます。
詳細については公式ドキュメントを確認してください。

$ pwd
/Users/rhoboro
$ python3 pkg.module
['/Users/rhoboro', '/Library/Frameworks/Python.framework/Versions/3.9/lib/python39.zip', '/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9', '/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages']

tags: python