Select Language

AI Technology Community

8.8、Python subprocessモジュールの使い方の詳細

Python 2.7 および Python 3 では、システムには subprocess モジュールが付属しています。このモジュールは主に子プロセスを管理するために使用されます。

このモジュールを使用する前に、以下の方法でインポートする必要があります:

import subprocess

以前は、新しいプロセスを起動するには os.system() メソッドを使用できました。subprocess モジュールは、os.system() よりも柔軟性が高く、完全に置き換えることができます。以下の例は、入力されたパラメータが返される shell スクリプトを作成するものです。

#! /bin/bash
echo "sub process is running"    # 標準出力に一行を表示
if [ $# != 1 ]                   # パラメータがない場合、戻り値は0
then
        exit 0
else                              # それ以外の場合は、戻り値はパラメータ
        exit $1
fi

実行結果は以下の通りです:

$ ./callSubprocess.sh 1 # 戻り値を1に指定 sub process is running $ echo $? # 戻り値を確認 1 $ ./callSubprocess.sh 3 # 戻り値を3に指定 sub process is running $ echo $? # 戻り値を確認 3 $ ./callSubprocess.sh sub process is running $ echo $? 0


次に、subprocess.call() を使用してプロセスを起動します。このインターフェース関数は、コマンドの各部分を表すパラメータのリストを受け取ります。たとえば、コマンドが ls -l の場合、["ls", '-l'] というリストで表すことができます。

>>> import subprocess
# コマンドを実行する、例えば bash callSubprocess.sh
>>> subprocess.call(["bash", "callSubprocess.sh"])
sub process is running                          # 標準出力
0                                             # プロセスの戻り値
# コマンド bash callSubprocess.sh 1 を実行する
>>> subprocess.call(["bash", "callSubprocess.sh", "1"])
sub process is running                          # 標準出力
1                                             # プロセスの戻り値
# コマンド bash callSubprocess.sh 3 を実行する
>>> subprocess.call(["bash", "callSubprocess.sh", "3"])
sub process is running                          # 標準出力
3                                             # プロセスの戻り値

call() の他に、subprocess モジュールには、他の手続き型のインターフェース関数も用意されています。

手続き型のインターフェース関数

手続き型のインターフェース関数とは、クラスインスタンスを作成することなく直接使用できるインターフェース関数のことです。

1) call(コマンドライン引数、タイムアウト時間)

この関数は前述の通り使用されています。パラメータは文字列またはリストのいずれかです。この関数は、子プロセスが終了するまで(または指定された時間まで)待機します。戻り値は子プロセスの戻り値であり、子プロセスの出力は現在のプロセスの出力に表示されます。

>>> retcode = subprocess.call("pwd")     # 文字列を使用
/Users/love,python/work
>>> retcode                             # 子プロセスの戻り値
0
>>> retcode = subprocess.call(["cat", "a.txt"])  # リストを使用
Tue Jul  2 09:22:09 CST 2019
>>> retcode                             # 子プロセスの戻り値
0

2) check_call(コマンドライン引数、タイムアウト時間)

この関数の使い方は call() と同じですが、子プロセスの戻り値が 0 でない場合、CalledProcessError 例外が発生します。

以下は、存在しないファイルを削除する rm コマンドの例です。この場合、戻り値は 1 になります。

$ rm nonexist.txt
rm: nonexist.txt: No such file or directory
$ echo $?
1

以下は、subprocess を使用して同じ操作を行うコードです:

# 上記の1行目のコマンドを実行する
>>> retcode = subprocess.call(["rm", "nonexist.txt"])
rm: nonexist.txt: No such file or directory     # 子プロセスの出力
>>> retcode                             # 戻り値を確認する、上記の3行目のコマンドと同じ
1
# check_call() を使用する
>>> retcode = subprocess.check_call(["rm", "nonexist.txt"])
rm: nonexist.txt: No such file or directory     # 子プロセスの出力
Traceback (most recent call last):              # 例外が発生しました
  File "<stdin>", line 1, in <module>
  File "/anaconda3/lib/python3.7/subprocess.py", line 347, in check_call
    raise CalledProcessError(retcode, cmd)
  subprocess.CalledProcessError: Command '['rm', 'nonexist.txt']'
  returned non-zero exit status 1.

3) check_output(コマンドライン引数、タイムアウト時間)

この関数は、子プロセスの出力内容を取得することができます。戻り値は子プロセスの出力です。

>>> ret = subprocess.check_output("date")      # 子プロセスの出力を取得する
>>> ret                                       # 戻り値は子プロセスの出力です
b'Tue Jul  2 13:29:41 CST 2019\n'
>>> type(ret)
<class 'bytes'>

標準出力とエラー出力のデータを同時に取得したい場合は、stderr=subprocess.STDOUT パラメータを追加することができます。これにより、エラー出力と標準出力が統合されます。たとえば、存在しないディレクトリを確認するために ls nonexist を使用すると、エラー出力にメッセージが表示されますが、標準出力には何も表示されません。以下の shell 実行プロセスはこの状況を示しています。

まず、以下の内容の shell スクリプトを作成します:

echo "stdout content"              # 標準出力に一行を出力する
echo "stderr content" 1>&2          # エラー出力に一行を出力する
exit 0                              # 戻り値を0に設定する

以下の方法でこのスクリプトを使用します:

# 標準出力を stdout.txt に、エラー出力を stderr.txt にリダイレクトする
$ ./stdout_err.sh 1>stdout.txt 2>stderr.txt
$ echo $?                          # 戻り値を確認する
0
$ cat stdout.txt                    # 標準出力の内容を確認する
stdout content
$ cat stderr.txt                    # エラー出力の内容を確認する
stderr content

エラー出力を標準出力に統合しない場合、以下のようになります:

>>> ret = subprocess.check_output(["bash", "./stdout_err.sh"])
stderr content                      # 捕捉されないエラー出力
>>> ret                             # 捕捉された標準出力
b'stdout content\n'

統合すると、以下のようになります:

>>> ret = subprocess.check_output(["bash", "./stdout_err.sh"],
                                stderr=subprocess.STDOUT)
>>> ret                             # 出力には標準出力とエラー出力が含まれます
b'stdout content\nstderr content\n'

このインターフェース関数は、子プロセスの戻り値をチェックします。戻り値が 0 でない場合、例外が発生します。たとえば、前述の shell スクリプトを変更して戻り値を 1 に設定すると、以下のようになります:

echo "stdout content"
echo "stderr content" 1>&2
exit 1                              # 戻り値を1に変更する

このスクリプトを実行すると、以下の出力が得られます:

# 変更後のスクリプトを使用する
>>> ret = subprocess.check_output(["bash", "./stdout_err_2.sh"],
                                stderr=subprocess.STDOUT)
Traceback (most recent call last):  # 例外が発生しました
  File "<stdin>", line 1, in <module>
  File "/anaconda3/lib/python3.7/subprocess.py", line 395, in check_output
    **kwargs).stdout
  File "/anaconda3/lib/python3.7/subprocess.py", line 487, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['bash', './stdout_err_2.sh']'
returned non-zero exit status 1.
>>> ret                             # 戻り値は正常です
b'stdout content\nstderr content\n'

4) getoutput():戻り値をチェックせずに出力のみを取得する

check を含むインターフェース関数は、戻り値をチェックします。戻り値をチェックしたくない場合は、getoutput() インターフェース関数を使用できます。このインターフェース関数は、文字列のコマンドを受け取り、別の shell を起動してコマンドを実行します。

>>> ret = subprocess.getoutput("./stdout_err_2.sh")
>>> ret                             # 子プロセスの標準出力とエラー出力の内容
'stdout content\nstderr content'

5) getstatusoutput():戻り値と出力を取得する

戻り値と出力の文字列を同時に取得したい場合は、getstatusoutput() を使用できます。この関数は、文字列で表されるコマンドを受け取り、タプルを返します。最初の要素は戻り値で、2番目の要素は標準出力とエラー出力が混合された文字列です。

# 戻り値と出力を同時に取得する
>>> ret = subprocess.getstatusoutput("./stdout_err_2.sh")
>>> ret                             # タプルが返されます。最初の要素は戻り値、2番目は出力
(1, 'stdout content\nstderr content')

Popenクラス

Popen クラスには、子プロセスの戻り値を取得したり、子プロセスの状態を照会したりするなど、多くの機能があります。このクラスの初期化関数には非常に多くのパラメータがありますが、ほとんどの場合、最初のパラメータ args のみを使用します。このパラメータはコマンドラインです。

以下は、Popen オブジェクトを作成して実行を開始する方法です:

>>> spp_obj = subprocess.Popen("date")
Mon Jul  1 23:05:27 CST 2019       # 子プロセスの標準出力の内容

コマンドにパラメータが含まれる場合は、リストを args に渡すことができます。たとえば、echo "love Python" コマンドを実行したい場合は、["echo", "love Python"] というリストを入力できます。以下のコードを参照してください。

>>> spp_obj = subprocess.Popen(args=["echo", "love Python"])
love Python                         # 子プロセスの出力

一般的な問題として、Linux および macOS システムでは、args パラメータに "cat a.txt" のような文字列を渡すと、実行時にエラーが発生することがあります。args が文字列の場合、文字列全体が実行可能ファイルであることが期待され、分割された最初の部分が実行可能ファイルであることは期待されません。これにより、"cat a.txt" ファイルが見つからないエラーが発生します。したがって、実行可能ファイルにパラメータがある場合は、必ずリストで渡す必要があります。

ただし、Windows プラットフォームではこの問題は発生しません。これは、プラットフォームごとの実装方法が異なるためです。

もう1つ

post
  • 10

    item of content
プロセスはリソースを分配する単位であり、スレッドはオペレーティングシステムがスケジューリングできる最小の単位です。
通常、プロセスには少なくとも1つのスレッドが含まれ、複数のスレッドがある場合はその中にメインスレッドが含まれます。同じプロセス内のすべてのスレッドはシステムリソースを共有しますが、それぞれが独立したスタック、レジスタ環境、およびローカルストレージを持っています。
マルチスレッドの利点は、複数のタスクを同時に実行できることです。システムに複数の計算ユニットがある場合、複数のスレッドはそれぞれの計算ユニットで並行して動作することができ、これによりシステムの処理効率が大幅に向上します。
多くの場合、プロセスはスレッドよりも大きい単位であり、通常1つのプロセスは複数のスレッドを含むことができます。プロセスの隔離効果はスレッドよりも優れているため、マルチプロセスを使用するとマルチスレッドよりも安全です。ただし、マルチプロセスの欠点はマルチスレッドよりもスケジューリングが重く、効率が低いことです。