2009-10-09 17 views
5

私はPythonのsubprocess.Popenメソッドに問題があります。子プロセスが終了するまでサブプロセス。ポップンが待機しないのはなぜですか?

ここに問題を示すテストスクリプトがあります。それはLinuxのボックスで実行されています。

#!/usr/bin/env python 
import subprocess 
import time 

def run(cmd): 
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) 
    return p 

### START MAIN 
# copy some rows from a source table to a destination table 
# note that the destination table is empty when this script is run 
cmd = 'mysql -u ve --skip-column-names --batch --execute="insert into destination (select * from source limit 100000)" test' 
run(cmd) 

# check to see how many rows exist in the destination table 
cmd = 'mysql -u ve --skip-column-names --batch --execute="select count(*) from destination" test' 
process = run(cmd) 
count = (int(process.communicate()[0][:-1])) 

# if subprocess.Popen() waited for the child to terminate than count should be 
# greater than 0 
if count > 0: 
    print "success: " + str(count) 
else: 
    print "failure: " + str(count) 
    time.sleep(5) 

    # find out how many rows exists in the destination table after sleeping 
    process = run(cmd) 
    count = (int(process.communicate()[0][:-1])) 
    print "after sleeping the count is " + str(count) 

通常、このスクリプトの出力は次のようになります。

success: 100000 

時にはそれは、障害の場合には、すぐに挿入後の選択が0の行を示していますが後ということ

failure: 0 
after sleeping the count is 100000 

注意です5秒間スリープすると、2番目のセレクトで100000という行数が正しく表示されます。私の結論は、次のいずれかが当てはまることです。

  1. subprocess.Popenが終了する子スレッドを待っていない - これは、MySQLのインサートがアトミックではありません文書
  2. と矛盾するようだ - mysqlのの私の理解では、インサートを示すように見えるが選択
  3. アトミックではありませんすぐに正しい行数を見る - 私よりも優れているmysqlを知っている友人によれば、どちらも起こらないはずです

私は何が欠けていますか?

参考までに、これはPythonのmysqlとやりとりするためのハックな方法だと思っていますが、MySQLdbにはこの問題はないようですが、なぜこの方法がうまくいかないのか不思議です。

+0

すてきな答えをお寄せいただきありがとうございます。サブプロセスのドキュメントをもう一度見てみると、Popenメソッドのセクションではなく便利なメソッドのセクションに表示される "コマンドの完了を待つ"というコメントが表示されています。 私は、私のオリジナルの質問に最もよく答えたので、私はJedの答えにうなずきました。私は将来のスクリプト作成のためにPaulのソリューションを使用すると思います。 –

+0

os.system(他の何かをしない限り)は、プロセスの戻り値(通常は0または1)を返します。あなたもそれに噛ませてはいけません。 –

答えて

20

subprocess.Popenは、インスタンス化するとプログラムを実行します。しかし、それを待つことはありません。シェルの中にcmd &と入力したようにバックグラウンドで起動します。上のコードでは、基本的に競合条件を定義しています。挿入が時間通りに完了できる場合は正常に表示されますが、そうでない場合は予期しない出力が発生します。あなたの最初のrun()のPIDが終了するのを待っているわけではなく、そのPopenインスタンスを戻して続行しているだけです。

私は同じように、それは待っていないであることを示すように見えるpopenの上のいくつかの非常に明確な方法がありますので、この動作は、ドキュメントと矛盾するかどうかはわかりません。

Popen.wait() 
    Wait for child process to terminate. Set and return returncode attribute. 

私がいること、しかし、同意しませんこのモジュールのドキュメントを改善することができました。プログラムが終了するのを待つために

、私はsubprocessの便利なメソッド、subprocess.call、または(あなたが標準出力を必要とする場合のために)Popenオブジェクトにcommunicateを使用してを使用してお勧めします。すでに2回目の通話をしています。

### START MAIN 
# copy some rows from a source table to a destination table 
# note that the destination table is empty when this script is run 
cmd = 'mysql -u ve --skip-column-names --batch --execute="insert into destination (select * from source limit 100000)" test' 
subprocess.call(cmd) 

# check to see how many rows exist in the destination table 
cmd = 'mysql -u ve --skip-column-names --batch --execute="select count(*) from destination" test' 
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) 
try: count = (int(process.communicate()[0][:-1])) 
except: count = 0 

さらに、ほとんどの場合、シェルでコマンドを実行する必要はありません。これはそのようなケースの1つですが、シーケンスのようにコマンドを書き直す必要があります。そのようにやっても、あなたは伝統的なシェルの注入を回避し、そうのように、引用についてはあまり心配することができます:

prog = ["mysql", "-u", "ve", "--execute", 'insert into foo values ("snargle", 2)'] 
subprocess.call(prog) 

これでも動作しますが、あなたが期待するよう注入しないでしょう。

prog = ["printf", "%s", "<", "/etc/passwd"] 
subprocess.call(prog) 

をインタラクティブに試してみてください。特にユーザーの入力を受け入れる場合は、シェル注入の可能性を避けてください。^

+1

私はsubprocess.callを使用していますが、待機中ではないようです。その後のステートメントは、実行したばかりのファイルを削除するようにコードに指示し、コードを実行する前に呼び出され、プログラムがクラッシュします。 – Elliot

4

subprocess.Popenwaitメソッドを使ってオブジェクトを返したのですが、それはなぜあなたが考えていなかったのでしょうか?私はあなたがサブプロセスと通信する方法がそれほど素晴らしいとは思っていません。あなたが推測しているように、待っているのはではなく、の暗黙的、内在的、即時的、必然的なものでした。サブプロセスを生成する最も一般的な理由は、処理が完了するまですぐに待つのではなく、処理を進める(たとえば、別のコアで、またはタイムスライスで最悪の場合、オペレーティングシステムやハードウェアの見通し)親プロセスと同時に実行します。親プロセスがサブプロセスが終了するのを待つ必要があるときは、元のsubprocess.Process呼び出しによって返されたオブジェクトに対して、明らかにwaitを呼び出します。

7

サブプロセスとpopenを絶対に使用する必要がない場合は、通常os.systemを使用する方が簡単です。あなたのプロセスは、スクリプトの次の段階に移る前に戻るの

import os 
run = os.system #convenience alias 
result = run('mysql -u ve --execute="select * from wherever" test') 

のpopenとは異なり、os.system待つん:たとえば、迅速なスクリプトのために私は頻繁にこのような何かを行います。

ドキュメントの詳細:http://docs.python.org/library/os.html#os.system

関連する問題