【问题标题】:Stop reading process output in Python without hang?在没有挂起的情况下停止在 Python 中读取进程输出?
【发布时间】:2011-05-24 00:13:34
【问题描述】:

我有一个适用于 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

它比第一个效果更好(它被 kelled 了),但它返回了:

<subprocess.Popen object at 0x97a50cc>

二审:

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

并保存在变量“进程”中。任何我的想法伙计们,我现在真的被困住了吗?

【问题讨论】:

  • subprocess.Popen 对象没有属性readlines

标签: python subprocess multiprocessor hung


【解决方案1】:

我建议不要使用“top”,而是使用“ps”,它会为您提供相同的信息,但只有一次,而不是永远每秒一次。

您还需要在 ps 中使用一些标志,我倾向于使用“ps aux”

【讨论】:

  • top 只是这类程序的完美示例
  • 所以...你没有使用top?你用的是什么程序?
  • airodump-ng 它是Aircrack-ng工具中的一个工具,它的输出类似于top
【解决方案2】:

我会做的,而不是这种方法,是检查您尝试从中获取信息的程序并确定该信息的最终来源。它可能是 API 调用或设备节点。然后,编写一些从同一来源获取它的 python。这消除了“抓取”“熟”数据的问题和开销。

【讨论】:

    【解决方案3】:
    #!/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's answer

    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 系统。如果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()
    

    此变体既不使用线程,也不使用信号,但它会在终端中产生乱码输出。如果process.stdout.readline() 阻塞,它将阻塞。

    【讨论】:

    • 非常感谢,太好了
    【解决方案4】:

    (J.F. Sebastian 你的代码效果很好,我认为它比我的解决方案更好=))

    我已经用另一种方法解决了。

    我没有直接在终端上输出,而是将其放入文件“tmp_file”:

    top >> tmp_file
    

    然后我使用“cut”工具将其输出“这是顶级输出”作为进程的值

    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.上面的代码有很多问题,例如,os.popen() 不应该像代码 2 中那样使用。我已经更新了我的答案以包含一个打印 all 程序输出的变体(不仅仅是指定的数字行数)stackoverflow.com/questions/4417962/…
    【解决方案5】:

    事实上,如果你填满输出缓冲区,你会得到一些答案。所以一种解决方案是用大量垃圾输出填充缓冲区(大约 6000 个字符,bufsize=1)。

    假设你有一个在 sys.stdout 上写入的 Python 脚本,而不是 top:

    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 之外的任何值都会使事情变得更糟。

    【讨论】:

      猜你喜欢
      • 2018-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-31
      • 2011-04-25
      • 1970-01-01
      • 2014-02-13
      • 1970-01-01
      相关资源
      最近更新 更多