7

他のプログラムと対話できる python プログラムを作成しようとしています。これは、stdin を送信し、stdout データを受信することを意味します。pexpect を使用することはできません (ただし、デザインの一部に影響を与えたことは間違いありません)。私が現在使用しているプロセスは次のとおりです。

  1. サブプロセスの stdout に pty をアタッチします
  2. チェックしてサブプロセスが終了するまでループしますsubprocess.poll
    • stdout に利用可能なデータがある場合、そのデータを現在の stdout にすぐに書き込みます。
  3. 終了!

私は動作するいくつかのコード (以下) のプロトタイプを作成していますが、私を悩ませている 1 つの欠陥があるようです。使用時にタイムアウトを指定しないと、子プロセスが完了した後、親プロセスがハングしますselect.select。タイムアウトを設定したくないのです。少しだけ汚れているようです。ただし、この問題を回避するために私が試みた他のすべての方法は機能していないようです。Pexpect は、 and の代わりに and を使用して、私が好まないソリューションを使用することで回避してos.execvいるpty.forkようsubprocess.Popenですpty.openpty。サブプロセスの寿命をチェックする方法に何か問題がありますか? 私のアプローチは間違っていますか?

私が使用しているコードは以下のとおりです。私はこれを Mac OS X 10.6.8 で使用していますが、Ubuntu 12.04 でも動作させる必要があります。

これはサブプロセスランナーrunner.pyです:

import subprocess
import select
import pty
import os
import sys

def main():
    master, slave = pty.openpty()

    process = subprocess.Popen(['python', 'outputter.py'], 
            stdin=subprocess.PIPE, 
            stdout=slave, stderr=slave, close_fds=True)

    while process.poll() is None:
        # Just FYI timeout is the last argument to select.select
        rlist, wlist, xlist = select.select([master], [], [])
        for f in rlist:
            output = os.read(f, 1000) # This is used because it doesn't block
            sys.stdout.write(output)
            sys.stdout.flush()
    print "**ALL COMPLETED**"

if __name__ == '__main__':
    main()

これはサブプロセス コードoutputter.pyです。奇妙なランダム部分は、ランダムな間隔でデータを出力するプログラムをシミュレートするためのものです。必要に応じて削除できます。それは問題ではありません:

import time
import sys
import random

def main():
    lines = ['hello', 'there', 'what', 'are', 'you', 'doing']
    for line in lines:
        sys.stdout.write(line + random.choice(['', '\n']))
        sys.stdout.flush()
        time.sleep(random.choice([1,2,3,4,5])/20.0)
    sys.stdout.write("\ndone\n")
    sys.stdout.flush()

if __name__ == '__main__':
    main()

皆様のご協力に感謝いたします。

追記

stdout がバッファリングされないようにするため、pty が使用されます。

4

4 に答える 4

12

まず第一に、os.readあなたが述べたことに反して、ブロックします。ただし、 の後にブロックしませんselect。またos.read、閉じたファイル記述子では常に空の文字列が返されるため、確認する必要がある場合があります。

ただし、実際の問題は、マスターデバイス記述子が閉じられないため、selectブロックされるのは最終的なものです。まれな競合状態で、子プロセスが と の間selectprocess.poll()終了し、プログラムが正常に終了します。ただし、ほとんどの場合、選択は永久にブロックされます。

izhak の提案に従ってシグナル ハンドラをインストールすると、すべての地獄が解き放たれます。子プロセスが終了するたびに、シグナル ハンドラが実行されます。シグナル ハンドラーが実行された後、そのスレッドの元のシステム コールを続行できないため、syscall の呼び出しはゼロ以外の errno を返し、Python でランダムな例外がスローされることがよくあります。現在、プログラムの他の場所で、そのような例外を処理する方法がわからないブロッキング システム コールを含むライブラリを使用している場合、大きな問題が発生します (os.readたとえば、どこでも、成功した後でも例外をスローできますselect)。

少しポーリングに対してランダムな例外がスローされることを考慮して、タイムアウトselectがそれほど悪い考えに聞こえないとは思いません。とにかく、あなたのプロセスがシステム上の唯一の(遅い)ポーリングプロセスになることはほとんどありません。

于 2012-09-01T06:10:54.423 に答える
9

コードを正しくするために変更できることがいくつかあります。私が考えることができる最も簡単なことは、フォーク後に親プロセスのスレーブfdのコピーを閉じることです。これにより、子が終了して自分のスレーブfdを閉じると、親select.select()はマスターを読み取り可能としてマークし、その後os.read()の空の結果を与えると、プログラムが完了します。(ptyマスターは、スレーブfdの両方のコピーが閉じられるまで、スレーブエンドが閉じられているとは見なしません。)

したがって、1行だけです。

os.close(slave)

..電話の直後に配置され、subprocess.Popen問題を解決する必要があります。

ただし、要件が正確に何であるかに応じて、おそらくより良い答えがあります。他の誰かが指摘したように、バッファリングを回避するためだけにptyは必要ありません。os.pipe()代わりにbareを使用できますpty.openpty()(そして戻り値をまったく同じように扱います)。裸のOSパイプは決してバッファリングしません。子プロセスがその出力をバッファリングしていない場合、あなたの呼び出しselect()os.read()呼び出しもバッファリングを認識しません。ただし、まだ行が必要os.close(slave)です。

ただし、さまざまな理由でptyが必要になる可能性があります。子プログラムの一部が多くの時間インタラクティブに実行されることを期待している場合、それらはstdinがptyであるかどうかを確認し、答えに応じて動作が異なる可能性があります(多くの一般的なユーティリティがこれを行います)。本当に子供に端末が割り当てられていると思わせたい場合は、ptyモジュールが最適です。実行方法によっては、使用からrunner.pyに切り替える必要がある場合があります。これにより、子のセッションIDが設定され、ptyが事前に開かれます(または、pty.pyのソースを参照して、その機能を確認し、適切なものを複製します。サブプロセスオブジェクトのpreexec_fn呼び出し可能のパーツ)。subprocesspty.fork()

于 2012-08-31T00:32:35.480 に答える
0

私が理解していることから、あなたはを使用する必要はありませんptyrunner.py次のように変更できます

import subprocess
import sys

def main():
        process = subprocess.Popen(['python', 'outputter.py'],
                        stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        while process.poll() is None:
                output = process.stdout.readline()
                sys.stdout.write(output)
                sys.stdout.flush()
        print "**ALL COMPLETED**"

if __name__ == '__main__':
        main()

process.stdout.read(1)process.stdout.readline()サブプロセスからの文字ごとのリアルタイム出力の代わりに使用できます。

注:サブプロセスからのリアルタイム出力が必要ない場合は、Popen.communicateを使用してポーリングループを回避してください。

于 2012-06-23T01:19:02.677 に答える
0

子プロセスが終了すると、親プロセスはSIGCHLDシグナルを受け取ります。デフォルトでは、このシグナルは無視されますが、インターセプトできます。

import sys
import signal

def handler(signum, frame):
    print 'Child has exited!'
    sys.exit(0)

signal.signal(signal.SIGCHLD, handler)

シグナルは、ブロックしている syscall を 'select' または 'read' (またはあなたがいるもの) に壊し、ハンドラー関数で必要なこと (クリーンアップ、終了など) を実行できるようにする必要があります。

于 2012-08-29T20:01:08.983 に答える