ハングなしでPythonでプロセス出力を読み取るのをやめますか?
-
08-10-2019 - |
質問
LinuxのPythonプログラムがあります。
import os
import time
process = os.popen("top").readlines()
time.sleep(1)
os.popen("killall top")
print process
プログラムはこのラインにかかっています:
process = os.popen("top").readlines()
そして、それは「TOP」のように更新出力を続け続けるツールで起こります
私の最高のトライアル:
import os
import time
import subprocess
process = subprocess.Popen('top')
time.sleep(2)
os.popen("killall top")
print process
それは最初のものよりもうまく機能しました(それはケレッドです)が、それは戻ります:
<subprocess.Popen object at 0x97a50cc>
2番目のトライアル:
import os
import time
import subprocess
process = subprocess.Popen('top').readlines()
time.sleep(2)
os.popen("killall top")
print process
最初のものと同じです。 「readlines()」のために絞首刑になりました
その帰還は次のようなものでなければなりません:
top - 05:31:15 up 12:12, 5 users, load average: 0.25, 0.14, 0.11
Tasks: 174 total, 2 running, 172 sleeping, 0 stopped, 0 zombie
Cpu(s): 9.3%us, 3.8%sy, 0.1%ni, 85.9%id, 0.9%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 1992828k total, 1849456k used, 143372k free, 233048k buffers
Swap: 4602876k total, 0k used, 4602876k free, 1122780k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
31735 Barakat 20 0 246m 52m 20m S 19.4 2.7 13:54.91 totem
1907 root 20 0 91264 45m 15m S 1.9 2.3 38:54.14 Xorg
2138 Barakat 20 0 17356 5368 4284 S 1.9 0.3 3:00.15 at-spi-registry
2164 Barakat 9 -11 164m 7372 6252 S 1.9 0.4 2:54.58 pulseaudio
2394 Barakat 20 0 27212 9792 8256 S 1.9 0.5 6:01.48 multiload-apple
6498 Barakat 20 0 56364 30m 18m S 1.9 1.6 0:03.38 pyshell
1 root 20 0 2880 1416 1208 S 0.0 0.1 0:02.02 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
3 root RT 0 0 0 0 S 0.0 0.0 0:00.12 migration/0
4 root 20 0 0 0 0 S 0.0 0.0 0:02.07 ksoftirqd/0
5 root RT 0 0 0 0 S 0.0 0.0 0:00.00 watchdog/0
9 root 20 0 0 0 0 S 0.0 0.0 0:01.43 events/0
11 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuset
12 root 20 0 0 0 0 S 0.0 0.0 0:00.02 khelper
13 root 20 0 0 0 0 S 0.0 0.0 0:00.00 netns
14 root 20 0 0 0 0 S 0.0 0.0 0:00.00 async/mgr
15 root 20 0 0 0 0 S 0.0 0.0 0:00.00 pm
変数「プロセス」を保存します。私のアイデアのみんな、私は今本当に立ち往生していますか?
解決
#!/usr/bin/env python
"""Start process; wait 2 seconds; kill the process; print all process output."""
import subprocess
import tempfile
import time
def main():
# open temporary file (it automatically deleted when it is closed)
# `Popen` requires `f.fileno()` so `SpooledTemporaryFile` adds nothing here
f = tempfile.TemporaryFile()
# start process, redirect stdout
p = subprocess.Popen(["top"], stdout=f)
# wait 2 seconds
time.sleep(2)
# kill process
#NOTE: if it doesn't kill the process then `p.wait()` blocks forever
p.terminate()
p.wait() # wait for the process to terminate otherwise the output is garbled
# print saved output
f.seek(0) # rewind to the beginning of the file
print f.read(),
f.close()
if __name__=="__main__":
main()
出力の部分のみを印刷するテールのようなソリューション
別のスレッドのプロセス出力を読み取り、キューに必要な最後の行の必要な数を保存できます。
import collections
import subprocess
import time
import threading
def read_output(process, append):
for line in iter(process.stdout.readline, ""):
append(line)
def main():
# start process, redirect stdout
process = subprocess.Popen(["top"], stdout=subprocess.PIPE, close_fds=True)
try:
# save last `number_of_lines` lines of the process output
number_of_lines = 200
q = collections.deque(maxlen=number_of_lines) # atomic .append()
t = threading.Thread(target=read_output, args=(process, q.append))
t.daemon = True
t.start()
#
time.sleep(2)
finally:
process.terminate() #NOTE: it doesn't ensure the process termination
# print saved lines
print ''.join(q)
if __name__=="__main__":
main()
このバリアントには必要です q.append()
原子操作になる。それ以外の場合、出力が破損している可能性があります。
signal.alarm()
解決
使用できます signal.alarm()
電話する process.terminate()
別のスレッドで読み取る代わりに指定されたタイムアウト後。それはあまりうまくやり取りしないかもしれませんが subprocess
モジュール。に基づく @Alex Martelliの答え:
import collections
import signal
import subprocess
class Alarm(Exception):
pass
def alarm_handler(signum, frame):
raise Alarm
def main():
# start process, redirect stdout
process = subprocess.Popen(["top"], stdout=subprocess.PIPE, close_fds=True)
# set signal handler
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(2) # produce SIGALRM in 2 seconds
try:
# save last `number_of_lines` lines of the process output
number_of_lines = 200
q = collections.deque(maxlen=number_of_lines)
for line in iter(process.stdout.readline, ""):
q.append(line)
signal.alarm(0) # cancel alarm
except Alarm:
process.terminate()
finally:
# print saved lines
print ''.join(q)
if __name__=="__main__":
main()
このアプローチは、 *nixシステムでのみ機能します。 IFをブロックする可能性があります process.stdout.readline()
戻りません。
threading.Timer
解決
import collections
import subprocess
import threading
def main():
# start process, redirect stdout
process = subprocess.Popen(["top"], stdout=subprocess.PIPE, close_fds=True)
# terminate process in timeout seconds
timeout = 2 # seconds
timer = threading.Timer(timeout, process.terminate)
timer.start()
# save last `number_of_lines` lines of the process output
number_of_lines = 200
q = collections.deque(process.stdout, maxlen=number_of_lines)
timer.cancel()
# print saved lines
print ''.join(q),
if __name__=="__main__":
main()
このアプローチは、Windowsでも動作するはずです。ここで使用しました process.stdout
反復可能として;追加の出力バッファリングを導入する可能性があります。 iter(process.stdout.readline, "")
それが望ましくない場合はアプローチします。プロセスが終了しない場合 process.terminate()
その後、スクリプトが掛けられます。
スレッド、信号ソリューションはありません
import collections
import subprocess
import sys
import time
def main():
args = sys.argv[1:]
if not args:
args = ['top']
# start process, redirect stdout
process = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True)
# save last `number_of_lines` lines of the process output
number_of_lines = 200
q = collections.deque(maxlen=number_of_lines)
timeout = 2 # seconds
now = start = time.time()
while (now - start) < timeout:
line = process.stdout.readline()
if not line:
break
q.append(line)
now = time.time()
else: # on timeout
process.terminate()
# print saved lines
print ''.join(q),
if __name__=="__main__":
main()
このバリアントはどちらのスレッドも信号も使用しませんが、端末に文字化けの出力を生成します。 IFをブロックします process.stdout.readline()
ブロック。
他のヒント
「トップ」を使用する代わりに、同じ情報を提供する「PS」を使用することをお勧めしますが、一度だけ永遠に一度だけではありません。
PSでいくつかのフラグを使用する必要があります。「PS AUX」を使用する傾向があります。
このアプローチではなく、私がすることは、情報を取得しようとしているプログラムを調べ、その情報の究極のソースを決定することです。 API呼び出しまたはデバイスノードかもしれません。次に、同じソースから取得したPythonをいくつか書きます。これにより、「スクレイピング」「調理された」データの問題とオーバーヘッドが排除されます。
(JFセバスチャンあなたのコードはうまく機能します、私の解決策よりも優れていると思います=))
私は別の方法でそれを解決しました。
ターミナルに出力を直接作成する代わりに、ファイル「tmp_file」になります。
top >> tmp_file
次に、プロセスの値としてトップ出力 "であるツール「カット」を使用して出力を作成しました
cat tmp_file
そして、それは私がやりたいことをしました。これは最終コードです:
import os
import subprocess
import time
subprocess.Popen("top >> tmp_file",shell = True)
time.sleep(1)
os.popen("killall top")
process = os.popen("cat tmp_file").read()
os.popen("rm tmp_file")
print process
# Thing better than nothing =)
助けてくれてありがとう
実際には、出力バッファーを埋めると、いくつかの答えが終了します。したがって、1つの解決策は、バッファーを大きなゴミ出力(bufsize = 1で〜6000文字)で埋めることです。
たとえば、トップの代わりに、sys.stdoutに書くPythonスクリプトがあるとしましょう。
GARBAGE='.\n'
sys.stdout.write(valuable_output)
sys.stdout.write(GARBAGE*3000)
ランチャー側では、単純なprocess.readline()の代わりに、
GARBAGE='.\n'
line=process.readline()
while line==GARBAGE:
line=process.readline()
2000年はサブプロセスの実装に依存しているため、少し汚れていると確信していますが、正常に機能し、非常に簡単です。 bufsize以外のものを設定= 1問題を悪化させます。