Alt işlem kullanarak gerçek zamanlı çıktı alma


135

İşlem için güzel bir ilerleme göstergesi gösterecek bir komut satırı programı (svnadmin doğrulaması) için bir sarmalayıcı komut dosyası yazmaya çalışıyorum. Bu, çıktı alınır alınmaz sarmalanmış programdan her çıktı satırını görebilmemi gerektiriyor.

Programı kullanarak çalıştıracağımı subprocess.Popen, kullanacağımı stdout=PIPE, sonra gelen her satırı okuyup ona göre hareket edeceğimi düşündüm . Bununla birlikte, aşağıdaki kodu çalıştırdığımda, çıktı bir yerde arabelleğe alınmış gibi görünüyordu, bu da iki parça halinde görünmesine neden oldu: 1'den 332'ye, sonra 333'ten 439'a (son çıktı satırı)

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE, 
        stderr = STDOUT, shell = True)
for line in p.stdout:
    print line.replace('\n', '')

Alt süreçle ilgili belgelere biraz baktıktan sonra, bufsizeparametresini keşfettim Popen, bu yüzden bufsize'yi 1 (her satırı tampon) ve 0 (tampon yok) olarak ayarlamayı denedim, ancak her iki değer de satırların teslim edilme şeklini değiştirmedi.

Bu noktada pipetleri kavramaya başladım, bu yüzden aşağıdaki çıktı döngüsünü yazdım:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

ama aynı sonucu aldı.

Alt süreç kullanılarak çalıştırılan bir programın 'gerçek zamanlı' program çıktısını almak mümkün müdür? Python'da ileriye uyumlu (olmayan exec*) başka bir seçenek var mı ?


1
sydout=PIPEAlt işlemin ana işlemi atlayarak doğrudan konsolunuza yazması için atlamayı denediniz mi?
S.Lott

5
Mesele şu ki çıktıyı okumak istiyorum. Doğrudan konsola çıktıysa, bunu nasıl yapabilirim? Ayrıca, kullanıcının sarmalanmış programdan çıktıyı görmesini istemiyorum, sadece çıktımı.
Chris Lieb

O zaman neden "gerçek zamanlı" bir ekran? Kullanım durumunu anlamadım.
S.Lott

8
Shell = True kullanmayın. Gereksiz yere kabuğunuzu çağırır. Bunun yerine p = Popen (['svnadmin', 'doğrula', '/ var / svn / repos / config'], stdout = PIPE, stderr = STDOUT) kullanın
nosklo

2
@ S.Lott Temel olarak, svnadmin doğrulaması, doğrulanan her revizyon için bir çıktı satırı yazdırır. Aşırı miktarda çıktıya neden olmayacak güzel bir ilerleme göstergesi yapmak istedim. Bir çeşit wget gibi, örneğin
Chris Lieb

Yanıtlar:


82

Bunu denedim ve bazı nedenlerden dolayı kod

for line in p.stdout:
  ...

tamponlar agresif bir şekilde, varyant

while True:
  line = p.stdout.readline()
  if not line: break
  ...

değil. Görünüşe göre bu bilinen bir hatadır: http://bugs.python.org/issue3907 (Sorun 29 Ağustos 2018 itibariyle artık "Kapalı")


Eski Python IO uygulamalarındaki tek karışıklık bu değil. Py2.6 ve Py3k'ın tamamen yeni bir IO kitaplığı oluşturmasının nedeni budur.
Tim Lin

3
Alt işlem boş bir satır döndürürse bu kod bozulur. Daha iyi bir çözüm kullanmak olacaktır while p.poll() is Noneyerine while Trueve kaldırmakif not line
exhuma

6
@exhuma: iyi çalışıyor. readline boş bir satırda "\ n" döndürür, bu doğru olarak değerlendirilmez. yalnızca boru kapandığında boş bir dize döndürür, bu, alt işlem sona erdiğinde olur.
Alice Purcell

1
@Dave Gelecek için ref: py2 + ile utf-8 satırlarını yazdırın print(line.decode('utf-8').rstrip()).
Jonathan Komar

3
Ayrıca, işlemin çıktısını gerçek zamanlı olarak okumak için, python'a herhangi bir tamponlama istemediğinizi söylemeniz gerekir. Sevgili Python bana doğrudan çıktıyı ver. Ve işte nasıl: Ortam değişkenini ayarlamanız gerekiyor PYTHONUNBUFFERED=1. Bu, özellikle sonsuz çıktılar için kullanışlıdır
George Pligoropoulos

38
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()

1
@nbro muhtemelen p.stdout.close()belirsiz olduğu için.
anatoly techtonik

1
@nbro muhtemelen kod açıklama yapılmadan verildiği için ...: /
Aaron Hall

3
Bu b '' ne hakkında?
ManuelSchneid3r

29

Alt işlem çıktısını doğrudan akışlara yönlendirebilirsiniz. Basitleştirilmiş örnek:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)

Bu, içeriği sonradan almanıza da izin veriyor .communicate()mu? Yoksa içerikler ana stderr / stdout akışlarına mı kayboldu?
theferrit32

Hayır, communicate()döndürülen yöntem yok CompletedProcess. Ayrıca, capture_outputile birlikte kullanılamaz stdoutve stderr.
Aidan Feldman

20

Bunu deneyebilirsiniz:

import subprocess
import sys

process = subprocess.Popen(
    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)

while True:
    out = process.stdout.read(1)
    if out == '' and process.poll() != None:
        break
    if out != '':
        sys.stdout.write(out)
        sys.stdout.flush()

Okuma yerine okuma satırı kullanırsanız, giriş mesajının yazdırılmadığı bazı durumlar olacaktır. Satır içi giriş gerektiren bir komutla deneyin ve kendiniz görün.


Evet, readline () kullanımı yazdırmayı durduracaktır (sys.stdout.flush () çağrılsa bile)
Ma

3
Bunun sonsuza kadar askıda kalması mı gerekiyor? Belirli bir çözümün, ilk alt işlem tamamlandığında döngüyü düzenlemek için standart kod içermesini de isterdim. Özür dilerim, kaç kez bakarsam bakayım, alt işlem vb. Hiçbir zaman işe koyamadığım bir şey.
ThorSummoner

1
Python'da, dışarıda değilken kullanabiliriz neden '' için test yapalım?
Greg Bell

2
bu, uzun süreli işler için en iyi çözümdür. ama kullanması gereken Yok ve değil! = Yok. Hiçbiri ile! = Kullanmamalısınız.
Cari

Stderr de bununla görüntüleniyor mu?
Pieter Vogelaar

7

Akış altişlem Stdin ve stdout'u ile asyncio Python içinde tarafından blog post Kevin McCarthy asyncio ile bunu nasıl gösterir:

import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec


async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break


async def run(command):
    process = await create_subprocess_exec(
        *command, stdout=PIPE, stderr=PIPE
    )

    await asyncio.wait(
        [
            _read_stream(
                process.stdout,
                lambda x: print(
                    "STDOUT: {}".format(x.decode("UTF8"))
                ),
            ),
            _read_stream(
                process.stderr,
                lambda x: print(
                    "STDERR: {}".format(x.decode("UTF8"))
                ),
            ),
        ]
    )

    await process.wait()


async def main():
    await run("docker build -t my-docker-image:latest .")


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

bu, gönderilen kodda küçük bir değişiklikle çalışır
Jeef

Merhaba @Jeef, cevabı güncelleyebilmem için düzeltmeyi gösterebilir misiniz?
Pablo

Merhaba, bu benim için çalıştı ama bazı hata mesajlarından kurtulmak için aşağıdakileri eklemem gerekiyordu: import nest_asyncio; nest_asyncio.apply()ve process = await create_subprocess_shell(*command, stdout=PIPE, stderr=PIPE, shell=True)bunun yerine kabuk komutunu kullanmak için process = await create_subprocess_exec(...). Şerefe!
user319436

4

Gerçek Zamanlı Çıktı Sorunu çözüldü: Python'da c programından gerçek zamanlı çıktıyı yakalarken benzer bir sorunla karşılaştım. " Fflush (stdout) " ekledim ; benim C kodumda. Benim için çalıştı. İşte kodun kırılması

<< C Programı >>

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}

<< Python Programı >>

#!/usr/bin/python

import os, sys
import subprocess


procExe = subprocess.Popen(".//count", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

while procExe.poll() is None:
    line = procExe.stdout.readline()
    print("Print:" + line)

<< ÇIKTI >> Yazdır: 1 Sayım Yazdır: 2 Sayım Yazdır: 3 Sayım

Umarım yardımcı olur.

~ Sairam


1
Bu gerçekten yardımcı olan tek şeydi. Aynı kodu ( flush(stdout)) C ++ 'da kullandım. Teşekkürler!
Gerhard Hagerer

Alt işlem olarak başka bir python betiğini çağıran bir python betiğiyle aynı sorunu yaşıyordum. Alt işlem baskılarında "flush" gerekliydi (python 3'te print ("merhaba", flush = True)). Ayrıca, birçok örnek hala var (2020) python 2, bu python 3, yani +1
smajtkst

3

Bir süre önce aynı problemle karşılaştım. Benim çözümüm, readyöntem için yinelemekten vazgeçmekti; bu , alt işleminizin yürütülmesi bitmemiş olsa bile hemen geri dönecektir.


3

Kullanım durumuna bağlı olarak, alt işlemin kendisinde de arabelleğe almayı devre dışı bırakmak isteyebilirsiniz.

Alt süreç bir Python süreci olacaksa, bunu aramadan önce yapabilirsiniz:

os.environ["PYTHONUNBUFFERED"] = "1"

Veya alternatif olarak bunu envargümanda iletin Popen.

Aksi takdirde, Linux / Unix üzerindeyseniz, stdbufaracı kullanabilirsiniz . Örneğin:

cmd = ["stdbuf", "-oL"] + cmd

Ayrıca bkz burada yaklaşık stdbufveya diğer seçenekler.

( Aynı cevap için buraya da bakınız .)


2

Bu çözümü bir alt işlemde gerçek zamanlı çıktı elde etmek için kullandım. Bu döngü, bir break ifadesine veya olası sonsuz döngüye ihtiyaç duymadan, işlem tamamlanır tamamlanmaz duracaktır.

sub_process = subprocess.Popen(my_command, close_fds=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

while sub_process.poll() is None:
    out = sub_process.stdout.read(1)
    sys.stdout.write(out)
    sys.stdout.flush()

5
stdout tamponu boş olmadan bunun döngüden çıkması mümkün mü?
jayjay

Tamamlandıktan sonra asılmayan uygun bir cevap için çok aradım! Ben ekleyerek çözüm olarak buldum if out=='': breaksonraout = sub_process...
Sos

2

Bu "tak ve çalıştır" işlevini burada buldum . Büyü gibi çalıştı!

import subprocess

def myrun(cmd):
    """from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html
    """
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = []
    while True:
        line = p.stdout.readline()
        stdout.append(line)
        print line,
        if line == '' and p.poll() != None:
            break
    return ''.join(stdout)

1
Eklenmesi stderr=subprocess.STDOUTaslında akış verileri yakalama çok yardımcı olur. Ben oy veriyorum.
khan

1
Buradaki ana sığır eti kabul edilen cevaptan
üçlü

2

Alt işlemin çıktısında her bayt üzerinde bir yineleyici kullanabilirsiniz. Bu, alt işlemden satır içi güncellemeye ('\ r' ile biten satırlar önceki çıktı satırının üzerine yazılır) izin verir:

from subprocess import PIPE, Popen

command = ["my_command", "-my_arg"]

# Open pipe to subprocess
subprocess = Popen(command, stdout=PIPE, stderr=PIPE)


# read each byte of subprocess
while subprocess.poll() is None:
    for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''):
        c = c.decode('ascii')
        sys.stdout.write(c)
sys.stdout.flush()

if subprocess.returncode != 0:
    raise Exception("The subprocess did not terminate correctly.")

2

Python 3.x'te, çıktı bir dize yerine bir bayt dizisi olduğu için işlem askıda kalabilir. Bir dizeye dönüştürdüğünüzden emin olun.

Python 3.6 başlayarak parametresini kullanarak bunu yapabilirsiniz encodingyılında POPEN Oluşturucu . Tam örnek:

process = subprocess.Popen(
    'my_command',
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    shell=True,
    encoding='utf-8',
    errors='replace'
)

while True:
    realtime_output = process.stdout.readline()

    if realtime_output == '' and process.poll() is not None:
        break

    if realtime_output:
        print(realtime_output.strip(), flush=True)

Not bu kod o yönlendirmeleri stderr için stdoutve kolları çıktı hataları .


1

Pexpect [ http://www.noah.org/wiki/Pexpect ] 'i engellemeyen okuma hatları ile kullanmak bu sorunu çözecektir. Bu, kanalların arabelleğe alınmasından ve dolayısıyla uygulamanızın çıktısının boru tarafından arabelleğe alınmasından kaynaklanır, bu nedenle, arabellek dolana veya işlem ölene kadar bu çıktıya ulaşamazsınız.


0

Tam çözüm:

import contextlib
import subprocess

# Unix, Windows and old Macintosh end-of-line
newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            while last not in newlines:
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
    )
    for line in unbuffered(proc):
        print line

example()

1
Görüşmede kullandığınız universal_newlines=Trueiçin, Popen()muhtemelen bunları kendi elinize almanıza da gerek yoktur - seçeneğin tüm noktası budur.
martineau

1
gereksiz karmaşık görünüyor. Arabelleğe alma sorunlarını çözmez. Cevabımdaki bağlantıları gör .
jfs

Gerçek zamanlı olarak rsync ilerleme çıktısını alabilmemin tek yolu budur (- outbuf = L)! teşekkürler
Mohammadhzp

0

Bunun için her zaman kullandığım temel iskelet budur. Zaman aşımlarının uygulanmasını kolaylaştırır ve kaçınılmaz asılı süreçlerle başa çıkabilir.

import subprocess
import threading
import Queue

def t_read_stdout(process, queue):
    """Read from stdout"""

    for output in iter(process.stdout.readline, b''):
        queue.put(output)

    return

process = subprocess.Popen(['dir'],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           bufsize=1,
                           cwd='C:\\',
                           shell=True)

queue = Queue.Queue()
t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))
t_stdout.daemon = True
t_stdout.start()

while process.poll() is None or not queue.empty():
    try:
        output = queue.get(timeout=.5)

    except Queue.Empty:
        continue

    if not output:
        continue

    print(output),

t_stdout.join()

0

(Bu çözüm Python 2.7.15 ile test edilmiştir
) Her satır okuma / yazma sonrasında sys.stdout.flush () yapmanız yeterlidir:

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()

0

Python 3.x veya pthon 2.x'i öneren az sayıda yanıt, Aşağıdaki kod her ikisi için de işe yarayacaktır.

 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,)
    stdout = []
    while True:
        line = p.stdout.readline()
        if not isinstance(line, (str)):
            line = line.decode('utf-8')
        stdout.append(line)
        print (line)
        if (line == '' and p.poll() != None):
            break
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.