python でコマンドを実行するには
subprocess モジュールを使う
以前にも書いたんだけど、気になったので、再度調べ直した。
suprocessでコマンドを実行する
単純にコマンドを実行するには、subprocess.call を使うのが楽ですね バッククォート ` やos.system のかわりに subprocess .call() を使うようです。
import subprocess cmd = "sleep 30" proc = subprocess.call( cmd , shell=True)
実行した結果はこちら。(シェル経由)
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND takuya 16804 0.0 0.0 20960 4076 pts/4 Ss 03:49 0:00 /bin/bash takuya 16864 1.0 0.0 23572 4864 pts/4 S+ 03:49 0:00 \_ python proc.py takuya 16865 0.0 0.0 4180 580 pts/4 S+ 03:49 0:00 \_ /bin/sh -c sleep 30 takuya 16866 0.0 0.0 5152 584 pts/4 S+ 03:49 0:00 \_ sleep 30
shell=True を渡したので、シェルsh -c 'sleep 30'
が実行されている。
この場合は、終了待ちをしています。call の部分でブロックされます。
PythonをCtrl+Cで止めたら、sh も一緒に止まりました。
シェル経由をしない場合
今度は、シェル経由をせずに、直接プロセスを起動する場合。
import subprocess cmd = "sleep 30" proc = subprocess.call( cmd .strip().split(" ") )
実行中のプロセスのツリーはこちら。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND takuya 16804 0.0 0.0 21112 4164 pts/4 Ss 03:49 0:00 /bin/bash takuya 16963 0.0 0.0 23572 4864 pts/4 S+ 03:53 0:00 \_ python proc.py takuya 16964 0.0 0.0 5152 584 pts/4 S+ 03:53 0:00 \_ sleep 30
シェル経由ではないので、コマンドとコマンド引数を配列で渡しています。
終了待ちをしています。callでブロックされます。Ctrl+Cで終了したら、sleep も殺してくれます。
Popen を用いる場合。
os.spawn の代わりに最近では Popenを用いるべきらしいです。
popen には使い方がいくつかあります。
単純に呼び出した場合。
import subprocess from subprocess import Popen from time import sleep cmd = "sleep 30" proc = Popen( cmd .strip().split(" ") )
単純に呼び出した場合のプロセスツリーです。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND takuya 16804 0.0 0.0 21112 4172 pts/4 Ss 03:49 0:00 /bin/bash takuya 17038 0.0 0.0 5152 584 pts/4 S 03:58 0:00 sleep 30
単純なPopenは終了待ちをせずにサブプロセスを起動するだけです。
親プロセスのPythonはsleep の終了待ちをせずに、sleepより先に終了してしまい、孤児プロセスとなったsleep はroot プロセス(init)に引き取られています。
import subprocess from subprocess import Popen from time import sleep cmd = "sleep 30" proc = Popen( cmd .strip().split(" ") ) sleep(5)
time.sleep を使って、起動直後の状態を確認してみます。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND takuya 16804 0.0 0.0 21112 4172 pts/4 Ss 03:49 0:00 /bin/bash takuya 17044 0.1 0.0 23572 4864 pts/4 S+ 03:58 0:00 \_ python proc.py takuya 17045 0.0 0.0 5152 584 pts/4 S+ 03:58 0:00 \_ sleep 30
起動直後では、親プロセスがPythonで子プロセスにsleep がちゃんと入っています。
起動後、親プロセスのPythonが先に終了するので 、子プロセスのsleep が孤児になるわけですね。
Popen+シェル経由で、サブプロセスを起動する。
今度は、シェル経由でサブプロセスを起動して、Pythonが終了待ちをしないPopenを見てみます。
import subprocess from subprocess import Popen from time import sleep cmd = "sleep 30" proc = Popen( cmd,shell=True )
Pythonが先に終了するので、sh -c が孤児プロセスとして引き取られていました。
takuya@atom:~$ ps uxf USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND takuya 16718 0.0 0.0 94388 1876 ? S 03:49 0:00 sshd: takuya@pts/0 takuya 16719 0.0 0.0 22816 6012 pts/0 Ss 03:49 0:00 \_ -bash takuya 16802 0.0 0.0 22656 1176 pts/0 S+ 03:49 0:00 \_ screen takuya 17138 0.0 0.0 4180 580 pts/4 S 04:04 0:00 /bin/sh -c sleep 30 takuya 17139 0.0 0.0 5152 584 pts/4 S 04:04 0:00 \_ sleep 30
シェル経由なので sh -c が出てきてます。
こちらも、起動後、親プロセスのPythonが先に終了するので 、子プロセスのsleep が孤児になるわけですね。
そのままでは終了を待ち合わせしません。
終了待ちをする Popen#wait を使う。
popen = Popen( cmd,shell=True )
popen.wait()
これで、Waitできる。でもPopenってなに?
Popen オブジェクトについて
Popenによってコマンドを実行し、サブプロセスを起動した場合はPopenオブジェクトが返却される。
Popenオブジェクトのインスタンスは、process ID や STDIN/STDOUT などを変数に持つプロセスを抽象化したオブジェクトのようです。
import subprocess from subprocess import Popen from time import sleep cmd = "sleep 30" proc = Popen( cmd,shell=True ) print( "process id = %s" % proc.pid )
実行結果は次のようになっていて
takuya@atom:~$ python proc.py process id = 17216
実行後のプロセスは次のようになっていました。
takuya@atom:~$ ps uxf USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND takuya 16718 0.0 0.0 94388 1876 ? S 03:49 0:00 sshd: takuya@pts/0 takuya 16719 0.0 0.0 22816 6012 pts/0 Ss 03:49 0:00 \_ -bash takuya 16802 0.0 0.0 22656 1176 pts/0 S+ 03:49 0:00 \_ screen takuya 17216 0.0 0.0 4180 580 pts/4 S 04:09 0:00 /bin/sh -c sleep 30 takuya 17217 0.0 0.0 5152 580 pts/4 S 04:09 0:00 \_ sleep 30
これから、間違いなくサブプロセスのプロセスIDを取得できていることが分かりました。
Popenオブジェクト便利ですね。
Popen#waitをしながら、Ctrl+Cを押した。
subprocess.call と同じように終了待ちをPopenで実現するには、 Popen#waitを使う。
Ctrl+Cでpythonを止めると、起動した子プロセスも一緒に死んでくれて便利。
孤児プロセスが出ないのは嬉しい。
import subprocess from subprocess import Popen from time import sleep cmd = "sleep 30" proc = Popen( cmd,shell=True ) print( "process id = %s" % proc.pid ) proc.wait()
ただし、Pythonが面倒見てくれるのはCtrl+Cを押した時だけ。
別のターミナルから、SIGINTを送信したら。
takuya@atom:~$ kill -INT 17343 takuya@atom:~$ ps uxf USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND takuya 17354 0.0 0.0 16388 1252 pts/5 R+ 04:17 0:00 \_ ps uxf takuya 16804 0.0 0.0 21268 4252 pts/4 Ss+ 03:49 0:00 /bin/bash takuya 16718 0.0 0.0 94388 1876 ? S 03:49 0:00 sshd: takuya@pts/0 takuya 16719 0.0 0.0 22816 6012 pts/0 Ss 03:49 0:00 \_ -bash takuya 16802 0.0 0.0 22656 1176 pts/0 S+ 03:49 0:00 \_ screen takuya 17344 0.0 0.0 4180 580 pts/4 S 04:16 0:00 /bin/sh -c sleep 30 ## initに引き取られた孤児プロセス takuya 17345 0.0 0.0 5152 584 pts/4 S 04:16 0:00 \_ sleep 30
TERM も INT も HUP も孤児が出来た。キーボードを押したら孫まで消してくれるのに。。。Ctrl+C押した場合とシグナル送信では若干動きが違う?
この辺は理由がわからないけど、ruby もそうだったので、Linuxのシグナルとプロセス管理をやっぱり勉強しなおしだね。
サププロセスを終了する Popen#terminate
teminate() を使うとSIGTERMシグナルが送信進されて、プロセスが終了される。
これもきちんと終了してくれる。
一定時間経過したらプロセスを終了するには
sleep と組み合わせて戦う。
import subprocess from subprocess import Popen from time import sleep cmd = "sleep 30" proc = Popen( cmd,shell=True ) print( "process id = %s" % proc.pid ) sleep(5) proc.terminate()
stack overflowには、Decoratorをと例外を使ってタイムアウトを実現する方法が紹介されていた。
Popenと wait と terminate を使う時の注意点
プロセスを扱うときに、PIPE.stdin/stdoutがデッドロックになる可能性があるので、注意しろとドキュメントに書いてあった。
terminate は SIGTERMを送信するが、WindowsはWin32APIの ProcessTerminate()を送信する。実行環境で違いがある。
実装系に依って動作が異なるようだ。
また、OSXで動かした時は shell=Trueをつけても、シェル経由にならなかった。
OSX で shell=True 付きの動作
takuya@~/Desktop$ pstree -s python -+= 00001 root /sbin/launchd \-+= 02974 takuya /Applications/iTerm.app/Contents/MacOS/iTerm2 \-+= 42127 takuya /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp takuya \-+- 42128 root login -fp takuya \-+= 42129 takuya -bash \-+= 42234 takuya /usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/Resources/Python.ap \--- 42235 takuya sleep 30
理由は良く分からないけど、shell=Trueをつけて試したがシェル経由にはらなかった。疑問が残った。
Pythonのsubprocess は実行環境次第なところがあるらしく、OSを変えたらチェックが必要かもしれない。
複数プロセスをパイプするとき
subprocess.PIPを使って、プロセスをパイプする。
cmd1 = "ls -lt " cmd2 = "head -n 5 " p1 = subprocess.Popen(cmd1.strip().split(" "), stdout=subprocess.PIPE) p2 = subprocess.Popen(cmd2.strip().split(" "), stdin=p1.stdout) p1.stdout.close() output = p2.communicate()[0]
comunicate を呼び出すと、waitになるし、stdout.close()して次に渡されるようですね。
p2が起動後に、p1のSTDOUTをclose()したらp1 がp2からのSIGPIPEを受け取れるのでパイプを使えるとのこと。
check_output でも出来るようです
output=check_output("ls -alt / | head -n 2", shell=True)
戻り値が byte 何だけど、str でほしいよ。
byte で出てくるので、decode が必要になったりして、めんどくさい。
ret = subprocess.check_output("ls -l ", shell=True,universal_newlines=True)
universal_newlines=True
をつけると unicode な str でもらえるよ。
プログラムにSTDINを渡したい。
ruby や phpや bashなどSTDINでソース・コードを渡せるスクリプトをpythonから実行するには
p1 = Popen(['php'],shell=False,stdin=subprocess.PIPE,stdout=subprocess.PIPE) p1.stdin.write(b'<?php echo "hello";') p1.stdin.close() p1.wait() ret = p1.stdout.read() print(str(ret, 'UTF-8'))
このように、手順を踏めば、pythonから外部スクリプトを実行して、その結果を変数に受け取ることができる。jsonなどで外部コードとやり取りすれば、プロセス間通信のようなことも可能になる。
まとめ
コマンドの実行
- 単純な呼び出し(終了待ちする系)
call ( "ls -l " , shell=True)
check_call ( "ls -l " , shell=True)
終了STATUSが0以外なら例外check_output ( "ls -l ", shell=True )
stdoutを取得
- Popenを使ったプロセス起動 (終了待ちなし)
popen = Popen( "ls -l" ,shell=True)
popen . wait ()
popen.terminate()
popen.communicate()[0]
stdout取得popen.reterncode
exit status 終了STATSUを取得popen .pid
プロセスIDを取得popen.poll()
終了しているか取得
- シェル経由かそうでないか
シェル経由
shell=True をつけて文字列で渡す非シェル経由
コマンドを配列で渡す。
関数 | popenを使って同等のこと |
---|---|
call | Popen( cmd_list ).wait() |
check_output | Popen( cmd_list ).communicate[0] |
追記
Popenの読み方がよくわからん。 Popen = p-open なのか、 Pop-en なのか、どっちなんだろう。
2016-04-25追記
split だと正規表現が使えないので
cmd .strip().split(" ")
正規表現を使う場合は
import re cmd = re.split('\s+', cmd.strip() )
のほうが良さそうです。
2019-09-03
str / byte の取り方について補足
参考資料
https://docs.python.org/2/library/subprocess.html#replacing-functions-from-the-popen2-module
http://stackoverflow.com/questions/2281850/timeout-function-if-it-takes-too-long-to-finish