Прекратить чтение процесса вывода в Python без зависания? - PullRequest
14 голосов
/ 11 декабря 2010

У меня есть программа на Python для Linux, которая выглядит примерно так:

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>

второе испытание:

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

и сохранить в переменной «process».Любая идея, ребята, я действительно застрял сейчас?

Ответы [ 5 ]

25 голосов
/ 11 декабря 2010
#!/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. Основано на ответе @ Алекса Мартелли :

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() блокируется.

3 голосов
/ 11 декабря 2010

Вместо использования "top" я предлагаю использовать "ps", которая даст вам ту же информацию, но только один раз, а не один раз в секунду для всей вечности.

Вам нужно будет также использовать некоторые флаги с ps, я обычно использую "ps aux"

0 голосов
/ 27 января 2013

На самом деле, если вы заполните буфер вывода, вы закончите с некоторым ответом.Поэтому одним из решений является заполнение буфера большим выводом мусора (~ 6000 символов с bufsize = 1).

Допустим, вместо top у вас есть скрипт на python, который пишет в sys.stdout:

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 усугубит ситуацию.

0 голосов
/ 12 декабря 2010

(Дж. Ф. Себастьян, ваши коды работают отлично, я думаю, что это лучше, чем мое решение =))

Я решил это другим способом.

Вместо вывода непосредственно на терминал я превращаю его в файл "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 =)

Большое спасибо, ребята, за помощь

0 голосов
/ 11 декабря 2010

Вместо этого подхода я бы хотел изучить программу, из которой вы пытаетесь получить информацию, и определить окончательный источник этой информации.Это может быть вызов API или узел устройства.Затем напишите некоторый Python, который получает его из того же источника.Это устраняет проблемы и накладные расходы по «очистке» «приготовленных» данных.

...