Python: バージョン・OS 透過的な subprocess モジュールの利用
Python のバージョン (2.6 / 2.7 / 3.2 / 3.3 / 3.4 / 3.5) や、OS環境 (Linux / Mac / Windows /CygWin) に依存せずに外部コマンドを実行するコードを書きたい。
ワークアラウンド
なかなか一筋縄ではいかない。
Python3.2 + POSIX環境 では、コマンドラインを bytes で渡してはいけない
以下のようなエラーが出る。
>>> import subprocess >>> subprocess.call(b'echo') TypeError: expect bytes or str, not int
subprocess モジュールの中で、args が文字列かどうかを判定するときに bytes である場合の考慮が不足しており、bytes の個々の要素(int)に対して分割が行われてしまうため。
対策は、コマンドラインをリストとして渡せばよい。以下のように対策する。(args が bytes である場合)
import sys if sys.version_info[:2] == (3, 2): args = [args]
Python3 + Windows環境では、コマンドラインと環境変数のキー・値を bytes で渡してはいけない
以下のようなエラーが出る。
>>> import subprocess >>> subprocess.call(b'cmd /C echo x') needquote = (" " in arg) or ("\t" in arg) or not arg TypeError: argument of type 'int' is not iterable
bytes に対する in オペレーションに失敗しているが、これといった対策が見当たらない。bytes で渡すのをやめて、str で渡すことにする。(その場合、コマンドライン文字列のエンコードは指定できない)
import sys import six if six.PY2 or sys.platform != 'win32': # この場合だけ bytes にエンコード
env の指定についても同様。キーだけではなく、値も bytes が許容されていないので注意。
POSIX環境 + shell=True のとき、args をリストとして渡してはいけない
リストの2番目以降の引数が評価されない。
>>> import subprocess >>> subprocess.call(['echo', '3'], shell=True) 0
ワークアラウンドとしては、subprocess.list2cmdline を使って適切な文字列を生成する。
import sys import subprocess if shell and sys.platform != 'win32': args = [subprocess.list2cmdline(args)]
外部コマンドについて
sleep
Windows 7 以降は「timeout」というコマンドが標準装備されているが、Python から実行すると標準入力が奪われてしまい不都合だった。
結局、Python の time モジュールを使うのが一番簡便だという結論に。
import subprocess subprocess.call('python -c "import time;time.sleep(2)"')
プロセステーブルの取得
- psutil 3.2.2 : Python Package Index を使うか
- 自分で実装するか
- Windows: TASKLIST /FO CSV /NH /FI PID eq プロセスID
- POSIX: os.kill(プロセスID, 0)
0 件のコメント:
コメントを投稿