Прекратите чтение Процесс вывода в Python без повешения?
-
08-10-2019 - |
Вопрос
У меня есть программа 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()
И это происходит в инструментах, которые поддерживают обновление, например «топ»
мои лучшие испытания:
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
так же, как первый. Это повесило из-за "Readleines ()"
Его возвращение должно быть так:
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 системах. Это может заблокировать, если 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()
Этот подход также должен работать на окнах. Здесь я использовал 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()
блоки.
Другие советы
Вместо того, чтобы использовать «TOP», я предлагаю использовать «PS», который даст вам ту же информацию, но только один раз вместо одного раз в секунду для всей вечности.
Вам нужно будет также использовать некоторые флаги с PS, я склонен использовать «PS Aux»
Что я бы сделал, а не этот подход, должен изучить программу, которую вы пытаетесь получить информацию и определить окончательный источник этой информации. Это может быть вызов API или узел устройства. Затем напишите немного Python, который получает его из того же источника. Это устраняет проблемы и накладные расходы «соскабливания» «приготовленные» данные.
(JF Sebastian Ваши коды отлично работают, я думаю, что это лучше, чем мое решение =))
Я решил его использовать другой путь.
Вместо того, чтобы сделать вывод прямо на терминал, я делаю его в файл "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 =)
Спасибо большое, ребята за помощью
Фактически, если вы заполните выходной буфер, вы закончите с некоторым ответом. Таким образом, одно решение - заполнить буфер с большим выходом мусора (~ 6000 символ с Bufsize = 1).
Скажем, вместо вершины у вас есть сценарий Python, который пишет на Sys.Stout:
GARBAGE='.\n'
sys.stdout.write(valuable_output)
sys.stdout.write(GARBAGE*3000)
На стороне пусковой установки вместо простого процесса.
GARBAGE='.\n'
line=process.readline()
while line==GARBAGE:
line=process.readline()
Вполне уверен, что это немного грязно, как 2000 год зависит от реализации подпроцесс, но это работает нормально и очень просто. Установка чего-либо, кроме Bufsize = 1 сделать дело хуже.