試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Pythonのsubprocessモジュールに関する追加メモ

Pythonで外部プロセスを起動して出力と戻り値を処理する」で以前扱ったPythonのsubprocessモジュールに関する追加のメモとなる。

subprocess.check_call()

subprocess.check_call()はsubprocess.Popenオブジェクトのコンストラクタ引数と同じ書式でコマンド行を実行し、正常に終了したら関数も正常終了するが、正常に終了しなかったときにsubprocess.CalledProcessError例外が発生するという便利なもの。

#! /usr/bin/python
# -*- coding: utf-8 -*-

import subprocess
import sys

print '-' * 80

# 子プロセスが正常に終了する例
try:
  subprocess.check_call(['uname', '-a'])
except subprocess.CalledProcessError, (p):
  print 'subprocess.CalledProcessError: cmd:%s returncode:%s' % (p.cmd, p.returncode)
  sys.exit(1)

print '-' * 80

# 子プロセスが正常に終了しない例
try:
  subprocess.check_call(['false',])
except subprocess.CalledProcessError, (p):
  print 'subprocess.CalledProcessError: cmd:%s returncode:%s' % (p.cmd, p.returncode)
  sys.exit(1)

パイプであるコマンドの出力を別のコマンドの入力として用いる

subprocess.Popenオブジェクトは生成時にstdin,stdout,stderrというキーワード引数をとるが、あるコマンド行を実行したときにそのsubprocess.Popenオブジェクトのメンバstdoutもしくはstderrの値を新しいsubprocess.Popenオブジェクトのstdinに指定することにより、1つ目のコマンド行の出力を2つ目のコマンドの入力として用いることができる。もちろん、同じ要領でシェルのパイプと同様に多段で渡すこともできる。

#! /usr/bin/python
# -*- coding: utf-8 -*-

import subprocess
import sys

# 「zcat /proc/config.gz | xz --best | base64 > config.xz.b64」を実行

# パイプで複数のコマンドを続けて渡していくには
# 「stdout=subprocess.PIPE」指定に加えて
# 2番目からのstdinを前のオブジェクトのメンバstdoutに指定する
try:
  p1 = subprocess.Popen(['zcat', '/proc/config.gz'],
                        stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT,
                        close_fds=True)
  p2 = subprocess.Popen(['xz', '--best'],
                        stdin=p1.stdout,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT,
                        close_fds=True)
  p3 = subprocess.Popen(['base64',],
                        stdin=p2.stdout,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT,
                        close_fds=True)
except OSError, (errno, msg):
  # shell=Falseで実行ファイルのパスが見つからない場合はOSError
  print >> sys.stderr, 'OSError[%s]: %s' % (errno, msg)
  sys.exit(1)

outfile = 'config.xz.b64'
try:
  f_out = open(outfile, 'w')
except IOError, (errno, msg):
  print >> sys.stderr, 'Error: cannot open output file "%s": %s' % (outfile, msg)
  sys.exit(1)

try:
  try:
    while True:
      line = p3.stdout.readline()
      if not line:
        break
      f_out.write(line)
  except IOError, (errno, msg):
    print >> sys.stderr, 'Error: cannot write to file "%s": %s' % (outfile, msg)
    sys.exit(1)
finally:
  f_out.close()
  exit_status = p3.wait()
print 'Exit status: %d' % exit_status

ただし、幾つものコマンド行を続けて実行する途中でOSErrorが発生した場合、上のように1つのtry節に複数のオブジェクト生成処理を続けて記述すると、例外の情報からはどのコマンドが失敗したのかが分からない(「No such file or directory」以上のことは不明)ので、失敗したときにどこで失敗したかを知る必要がある場合は1つずつtry-exceptを記述していくほうがよいかもしれない。
また、ファイルへの書き出しをするにはsubprocess.Popenオブジェクト生成時にファイルオブジェクトを指定することもできるが、この場合、子プロセスの動作自体は正常でも途中で書き込みに失敗したときに(wait()の)戻り値が0にならず、その詳細が分からずに悩むことになる可能性があるので、上の例では普通に出力を受け取ってファイルに書き出す形とした。

関連記事:

参考URL:

使用したバージョン: