Stop reading process output in Python without hang?
我有一个Linux的Python程序几乎看起来像这样:
1 2 3 4 5 6 7 8 9 10 | import os import time process = os.popen("top").readlines() time.sleep(1) os.popen("killall top") print process |
该程序挂起在这一行:
1 | process = os.popen("top").readlines() |
这种情况发生在保持更新输出的工具中,如"Top"
我最好的考验:
1 2 3 4 5 6 7 8 9 10 11 | import os import time import subprocess process = subprocess.Popen('top') time.sleep(2) os.popen("killall top") print process |
它比第一个(它被砍掉)效果更好,但它返回:
1 | <subprocess.Popen object at 0x97a50cc> |
第二次试验:
1 2 3 4 5 6 7 8 9 10 11 | import os import time import subprocess process = subprocess.Popen('top').readlines() time.sleep(2) os.popen("killall top") print process |
和第一个一样。 它由于"readlines()"而被绞死
它的返回应该是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 |
并保存在变量"process"中。 任何我想到的人,我现在真的被困住了吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/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() |
类似尾部的解决方案,只打印输出的一部分
您可以在另一个线程中读取进程输出并保存队列中所需的最后一行数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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() |
此变体要求
您可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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系统。如果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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。在这里,我使用
没有线程,没有信号解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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() |
此变体既不使用线程也不使用信号,但它会在终端中产生乱码输出。如果
而不是使用"顶部"我建议使用"ps",它会给你相同的信息,但只有一次,而不是每秒一次。
你还需要使用ps的一些标志,我倾向于使用"ps aux"
事实上,如果你填写输出缓冲区,你会得到一些答案。因此,一种解决方案是使用大垃圾输出填充缓冲区(?6000字符,bufsize = 1)。
比方说,你有一个在sys.stdout上写的python脚本,而不是top。
1 2 3 4 | GARBAGE='. ' sys.stdout.write(valuable_output) sys.stdout.write(GARBAGE*3000) |
在启动器端,而不是简单的process.readline():
1 2 3 4 5 | GARBAGE='. ' line=process.readline() while line==GARBAGE: line=process.readline() |
确定它有点脏,因为2000依赖于子进程实现,但它工作正常并且非常简单。设置除bufsize = 1之外的任何东西都会让事情变得更糟。
(J.F. Sebastian你的代码工作得很好,我认为它比我的解决方案更好=))
我用另一种方式解决了它。
而不是直接在终端上输出我把它变成文件"tmp_file":
1 | top >> tmp_file |
然后我使用工具"剪切"使其输出"最高输出"作为过程的值
1 | cat tmp_file |
它做了我想要它做的事。这是最终的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 =) |
非常感谢你们的帮助
我会做的,而不是这种方法,是检查你试图从中获取信息的程序,并确定该信息的最终来源。它可以是API调用或设备节点。然后,编写一些从同一个源获取它的python。这消除了"刮""熟"数据的问题和开销。