https://kohkimakimoto.hatenablog.com/entry/2018/01/19/095920
Webプログラミングなどについてあれこれ。
シェルスクリプトの代わりにPythonを使う
Bashスクリプトへの不満
- クラスや
連想配列がないので、構造化したデータが扱いづらい。 - JSONをパースできない。jqなどのシステムにデフォルトでインストールされていないコマンドが必要。
- 基本、コマンドの組み合わせでロジックを書いていくのだが、MacとLinuxで挙動が微妙に異なるコマンドがある。そのため思わぬ環境依存でハマることがある。
- 関数の戻り値が数値しか返せないので、結果を文字列で欲しいときは、標準出力とパイプを使うなど、いろいろ細かいテクニックが必要。
rbenv
: https://github.com/rbenv/rbenvheroku-buildpack-php
: https://github.com/heroku/heroku-buildpack-php
スクリプトの実装方針
私がスクリプトを書く際の、実装の基本方針は以下のようなものです。
- MacでもLinux(主にCentOS)でも動作する。
- なるべく、システムに標準でインストールされているコマンド以外に依存しない。
- なるべく、1ファイルで実装する。
-h
オプションでヘルプメッセージ表示に対応させて、必要なドキュメントも内包させる。
要は「Mac上で実装、デバックして、Linuxサーバ上で使う場合は、スクリプトファイル一個を置けば動作する」ように作りたいわけです。PythonはMacにもCentOSにもデフォルトで入っているし、標準ライブラリも豊富なので、このようなポータビリティ重視のスクリプト記述に向いていると思いました。
ポータビリティ重視のための縛りPythonプログラミング
- Pythonのバージョン2系と3系どちらでも、動作するように書く。
- 外部パッケージを使わない。
現在のPythonの主流はバージョン3系なので、基本は3系のコードを書きます。しかし、MacやCentOSにデフォルトインストールされているPythonは2系なので、そちらでも動くように、適宜ワークアラウンドを入れて、書きます。とはいえ、大がかりなアプリケーションを書くわけではないので、そこまで難しいものではありません。外部パッケージを使わないという縛りは、基本的に実装を1ファイルで済ませたいからです。あくまで、シェルスクリプトの代替として使いたい、というのが今回の目的です。
Tips
__future__
モジュールでバージョン2、3両方に対応させる
__future__
モジュールを以下のようにしてインポートします。個々の仕様の詳細は、もう忘れてしまいましたが、3系のコードを、2系のランタイムでも使用できるようにするものです。今は何も考えず、おまじないのように、スクリプトの最初に必ず記述する感じです。from __future__ import division, print_function, absolute_import, unicode_literals
バージョン2、3で異なるモジュールを、同じ名前で読み込む
ConfigParserは、iniファイル形式の設定ファイルを扱うための標準モジュールです。 Bashでは難しい、構造化された外部設定ファイルを扱えるので、便利です。ただ2系と3系でモジュールの名前が変わっているので、以下のようにして、最初に3系のモジュールをimport、失敗したら2系をimportするようにしてます。これで両方のバージョンで同じようにモジュールが使用できます。
try: import configparser as ConfigParser except ImportError: # fallback for python2 import ConfigParser config = ConfigParser.RawConfigParser() config.read("/path/to/configfile.conf")
バージョン2、3を判別する関数を用意する
どうしても2系と3系で、処理を切り分ける必要がでてくる場合もあります。このため、バージョンを判別できる関数を定義しておくと便利です。以下のように
runtime
クラスのスタティックメソッドとして実装します。class runtime: @staticmethod def v3(): return sys.version_info >= (3,) @staticmethod def v2(): return sys.version_info < (3,) if runtime.v3(): # バージョン3系のときの処理を書く...
よく使うスニペット
コピペして使っているスニペットをいくつか紹介します。全て、標準モジュールのみに依存するコードです。適宜
import os
やimport subprocess
などのように、事前に必要なモジュールをロードして使います。文字に色をつける
class colors: bold = '\033[1m' underlined = '\033[4m' black = '\033[30m' red = '\033[31m' green = '\033[32m' yellow = '\033[33m' blue = '\033[34m' magenta = '\033[35m' cyan = '\033[36m' lightgray = '\033[37m' darkgray = '\033[90m' lightred = '\033[91m' lightgreen = '\033[92m' lightyellow = '\033[93m' lightblue = '\033[94m' lightmagenta = '\033[95m' lightcyan = '\033[96m' background_black = '\033[40m' background_red = '\033[41m' background_green = '\033[42m' background_yellow = '\033[43m' background_blue = '\033[44m' background_magenta = '\033[45m' background_cyan = '\033[46m' reset = '\033[0m' # 使い方 print(colors.red + "red text" + colors.reset)
赤い文字でエラーメッセージを出力して終了する
前述の
colors
クラスを使います。def abort(s): print(colors.red + s + colors.reset, file=sys.stderr) sys.exit(1) abort("error!")
外部コマンド実行する
シェルスクリプトの代替としてのPythonなので、外部コマンド実行は、ほぼ必ず使います。
subprocess.check_output
を使うと、簡単にコマンド実行して標準出力を取得できます。 このメソッドの戻り値は2系と3系で異なるので、前述のrumtime
クラスによるバージョン判定を使って、以下のように利用します。out = subprocess.check_output("ls -la", shell=True).strip() if runtime.v3(): out = out.decode('utf-8')
テキストをファイルに出力する
fd = open("/path/to/file", 'w') fd.write("""#!/usr/bin/env bash set -e echo "generated by Python" """) fd.close()
ファイルに実行権限をつける
umask = os.umask(0) os.chmod("/path/to/file", 0o755) os.umask(umask)
スクリプトの同時、多重起動防止する
try: fd = open(__file__, 'r') fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: print("Another process is using: " + os.path.basename(__file__), file=sys.stderr) sys.exit(1)
コマンドライン・オプションの解析
argparse
モジュールでできます。以下は最小のサンプルです。import argparse parser = argparse.ArgumentParser( description="cli application description", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" additional description... """ ) parser.add_argument("-V", "--version", dest="version", action="store_true", help="Print the version.") args = parser.parse_args() if args.version: print("v0.0.1") sys.exit(0)
これだけで
-h
オプションで、ヘルプメッセージを表示に対応したコマンドができます。epilog
に使い方などを書けば、ちょっとしたドキュメント代わりになります。 ほかにも、gitのようなサブコマンドの作成にも対応していて、Bashに比べると、とても便利です。HTTPリクエスト
#!/usr/bin/env python from __future__ import division, print_function, absolute_import, unicode_literals try: from urllib.request import urlopen, Request from urllib.error import HTTPError except ImportError: # fallback for python2 from urllib2 import urlopen, Request, HTTPError import json import sys class runtime: @staticmethod def v3(): return sys.version_info >= (3,) @staticmethod def v2(): return sys.version_info < (3,) def main(): res = urlopen("https://api.github.com/users/kohkimakimoto/keys") body = res.read() if runtime.v3(): body = body.decode('utf-8') keys = json.loads(body) for key in keys: print(key['key']) if __name__ == '__main__': main()
0 件のコメント:
コメントを投稿