alt işlem stdout'unu satır satır oku


235

Python betiğim, çok gürültülü bir linux yardımcı programını çağırmak için alt süreci kullanır. Tüm çıktıları bir günlük dosyasına kaydetmek ve bir kısmını kullanıcıya göstermek istiyorum. Aşağıdakilerin işe yarayacağını düşündüm, ancak yardımcı program önemli miktarda çıktı üretene kadar çıktı uygulamamda görünmüyor.

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()

Gerçekten istediğim davranış, filtre komut dosyasının alt satırdan alındığı gibi her satırı yazdırmasıdır. Ne teegibi ama python kodu ile sorta.

Neyi kaçırıyorum? Bu mümkün mü?


Güncelleme:

sys.stdout.flush()Fake_utility.py dosyasına a eklenirse, kod python 3.1'de istenen davranışa sahiptir. Python 2.6 kullanıyorum. Kullanmanın proc.stdout.xreadlines()py3k ile aynı işe yarayacağını düşünürdünüz, ancak işe yaramaz.


Güncelleme 2:

İşte asgari çalışma kodu.

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()

4
print line,yerine kullanabilirsiniz print line.rstrip()(not: sonunda virgül).
jfs


2
Güncelleme 2, python 3.0+ ile çalıştığını, ancak eski yazdırma deyimini kullandığını, bu nedenle python 3.0+ ile çalışmadığını belirtir.
Rooky

Burada listelenen cevapların hiçbiri benim için işe yaramadı , ancak stackoverflow.com/questions/5411780/… işe yaramadı!
kutulu

Yanıtlar:


179

En son Python ile çalıştığımdan bu yana uzun zaman geçti, ama sorun, for line in proc.stdoutyinelemeden önce tüm girdiyi okuyan ifadeyle ilgili . Çözüm readline()bunun yerine kullanmaktır :

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()

Tabii ki hala alt sürecin arabelleğe alınmasıyla uğraşmak zorundasınız.

Not: Dokümantasyona göre , bir yineleyiciye sahip çözüm readline(), okuma önbelleği hariç, kullanıma eşdeğer olmalıdır , ancak (veya tam olarak bu nedenle) önerilen değişiklik benim için farklı sonuçlar üretti (Windows XP'de Python 2.5).


11
için file.readline()vs for line in filebakınız bugs.python.org/issue3907 ;: (kullanımını da Python3 çalışır kısacası io.open()2.6+ Python üzerine)
jfs

5
Bir EOF için PEP 8'deki "Programlama Önerileri" ( python.org/dev/peps/pep-0008 ) uyarınca daha fazla pythonic testi 'line değilse:' olacaktır.
Jason Mock

14
@naxa: Boru: for line in iter(proc.stdout.readline, ''):.
jfs

3
@ Jan-PhilipGehrcke: evet. 1. for line in proc.stdoutPython 3'te kullanabilirsiniz (okuma hatası yoktur) 2. '' != b''Python 3'te - kodu körü körüne kopyalayıp yapıştırmayın - ne yaptığını ve nasıl çalıştığını düşünün.
jfs

2
@JFSebastian: emin, iter(f.readline, b'')çözüm oldukça açıktır (ve ilgilenen varsa Python 2 üzerinde de çalışır). Yorumumun amacı, çözümünüzü suçlamak değil (böyle göründüğünde özür dilerim, şimdi de okudum!), Ancak bu durumda oldukça şiddetli olan semptomların boyutunu (Py2 / 3 konu istisnalara neden olurken, burada iyi davranan bir döngü sonsuz olarak değişti ve çöp toplama yeni oluşturulan nesnelerin seli ile mücadele ederek uzun süreli ve büyük genlikte bellek kullanım salınımları sağladı).
Dr.Jan-Philip Gehrcke

45

Partiye biraz geç, ama burada en basit çözüm olduğunu düşündüğümü görmemek şaşırdı:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line

(Bu Python 3 gerektirir.)


25
Bu cevabı kullanmak istiyorum ama alıyorum: AttributeError: 'file' object has no attribute 'readable' py2.7
Dan Garthwaite

3
Python 3 ile çalışır
Matanster

Açıkça bu kod py3 / py3 uyumluluğu ve ValueError alma gerçek riski için birden fazla nedenden dolayı geçerli değildir: Kapalı dosyada G / Ç işlemi
sorin

3
@sorin bunların hiçbiri "geçerli değil". Hala Python 2'yi desteklemesi gereken bir kütüphane yazıyorsanız, bu kodu kullanmayın. Ancak birçok insan on yıldan daha uzun bir süre önce piyasaya sürülen yazılımı kullanabilme lüksüne sahiptir. Kapalı bir dosyada okumaya çalışırsanız, kullansanız TextIOWrapperda kullanmasanız da bu istisnayı elde edersiniz . İstisnayı kolayca halledebilirsiniz.
jbg

1
belki partiye geç kaldın ama cevap Python, ty
Dusan Gligoric

20

Gerçekten de, yineleyiciyi sıraladıysanız, arabelleğe alma artık sizin probleminiz olabilir. Alt işlemdeki python'a çıktısını arabelleğe almamasını söyleyebilirsiniz.

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

olur

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

Python içinden python çağırırken buna ihtiyacım var.


14

Bu ekstra parametreleri şuraya geçirmek istiyorsunuz subprocess.Popen:

bufsize=1, universal_newlines=True

Ardından örneğinizde olduğu gibi yineleyebilirsiniz. (Python 3.5 ile test edilmiştir)


2
@nicoulaj subprocess32 paketi kullanılıyorsa çalışmalıdır.
Quantum7

4

Her iki yineleme sağlayan bir fonksiyondur stdoutve stderrsatır satır eş zamanlı gerçek zamanlı olarak

Eğer her ikisi için çıkış akışı almak gerekir stdoutve stderraynı zamanda, aşağıdaki işlevini kullanabilirsiniz.

İşlev, her iki Popen borusunu tek bir yineleyicide birleştirmek için Kuyrukları kullanır.

Burada işlevi yaratıyoruz read_popen_pipes():

from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
            except Empty:
                pass
            try:
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

read_popen_pipes() kullanımda:

import subprocess as sp


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):

        # Do stuff with each line, e.g.:
        print(out_line, end='')
        print(err_line, end='')

    return p.poll() # return status-code

2

Döngüsüz satırları da okuyabilirsiniz. Python'da çalışır 3.6.

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()

1
Veya dizelere dönüştürmek için:list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
ndtreviv

1

Bunu python3 ile denedim ve çalıştı, kaynak

def output_reader(proc):
    for line in iter(proc.stdout.readline, b''):
        print('got line: {0}'.format(line.decode('utf-8')), end='')


def main():
    proc = subprocess.Popen(['python', 'fake_utility.py'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)

    t = threading.Thread(target=output_reader, args=(proc,))
    t.start()

    try:
        time.sleep(0.2)
        import time
        i = 0

        while True:
        print (hex(i)*512)
        i += 1
        time.sleep(0.5)
    finally:
        proc.terminate()
        try:
            proc.wait(timeout=0.2)
            print('== subprocess exited with rc =', proc.returncode)
        except subprocess.TimeoutExpired:
            print('subprocess did not terminate in time')
    t.join()

1

Rômulo'nun cevabındaki aşağıdaki değişiklik benim için Python 2 ve 3'te (2.7.12 ve 3.6.1) çalışır:

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
  line = process.stdout.readline()
  if line != '':
    os.write(1, line)
  else:
    break

0

Alt işlem modülüne eklendiğinde Dunno, ancak Python 3 ile aşağıdakileri kullanmanız gerekir proc.stdout.splitlines():

for line in proc.stdout.splitlines():
   print "stdout:", line
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.