İşlem devam ederken alt süreç çıktısını sürekli yazdırın


204

Python betiklerimden program başlatmak için aşağıdaki yöntemi kullanıyorum:

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)

Yani bir işlemi başlattığımda Process.execute("mvn clean install"), programım işlem bitene kadar bekler ve ancak o zaman programımın tam çıktısını alırım. Bitirmek için biraz zaman alan bir işlem çalıştırıyorsam bu sinir bozucu.

Programımın işlem çıktısını bir döngü veya başka bir şeyle bitmeden önce yoklayarak satır satır yazmasına izin verebilir miyim?

** [EDIT] Üzgünüz, bu soruyu göndermeden önce çok iyi arama yapmadım. Diş açma aslında anahtardır. Burada nasıl yapılacağını gösteren bir örnek bulundu: ** Python Subprocess.


Sanırım iş parçacığı yerine iplik
Ant

9
Hayır, konulara ihtiyacınız yok. Tüm borulama fikri çalışır, çünkü çalışırken süreçlerden okuma / yazma yapabilirsiniz.
tokland

Yanıtlar:


266

Sen kullanabilirsiniz ITER : yakında komut çıkışları onları olduğunca satırları işlemek için lines = iter(fd.readline, ""). Tipik bir kullanım durumunu gösteren tam bir örnek (yardım için @jfs sayesinde):

from __future__ import print_function # Only Python 2.x
import subprocess

def execute(cmd):
    popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
    for stdout_line in iter(popen.stdout.readline, ""):
        yield stdout_line 
    popen.stdout.close()
    return_code = popen.wait()
    if return_code:
        raise subprocess.CalledProcessError(return_code, cmd)

# Example
for path in execute(["locate", "a"]):
    print(path, end="")

24
Bu kodu denedim (çalıştırmak için önemli zaman alan bir programla) ve yürütmenin tamamlanmasını beklemek yerine satırları alındıkları gibi onaylayabilir. Bu üstün cevap imo.
Andrew Martin

11
Not: Python 3'te kullanabilirsiniz for line in popen.stdout: print(line.decode(), end=''). Hem Python 2 hem de 3'ü desteklemek için, bayt değişmezini kullanın: b''aksi takdirde lines_iteratorPython 3'te asla bitmez.
jfs

3
Bu yaklaşımdaki sorun, süreç stdout'a hiçbir şey yazmadan biraz duraklarsa, okunacak daha fazla girdi olmamasıdır. İşlemin tamamlanıp tamamlanmadığını kontrol etmek için bir döngüye ihtiyacınız olacaktır. Python 2.7
Har

7
İşe yaramalı. Parlatmak için ekleyebilir bufsize=1(Python 2'deki performansı artırabilir), popen.stdoutboruyu açıkça (çöp toplama işleminin ilgilenmesini beklemeden ) kapatabilir ve yükseltebilirsiniz subprocess.CalledProcessError(gibi check_call(), yapın check_output()). printDeyim Python 2. ve 3. farklıdır: Eğer Softspace kesmek kullanabilirsiniz print line,Kodunuzdaki gibi tüm yeni satır iki katına önlemek için yaptığı ve geçen: (virgül not) universal_newlines=Trueyerine bytes- metnini almak için, Python 3 ile ilgili cevap .
jfs

6
@binzhang Bu bir hata değil, stdout varsayılan olarak Python komut dosyalarında (birçok Unix aracı için de) arabelleğe alınır. Deneyin execute(["python", "-u", "child_thread.py"]). Daha fazla bilgi: stackoverflow.com/questions/14258500/…
tokland

84

Tamam i iş parçacıkları olmadan çözmek başardı (iş parçacığı kullanarak neden daha iyi olacağını takdir) bu sorudan bir pasaj kullanarak takdir Bir alt işlem çalışırken stdout durdurma

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # Poll process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() is not None:
            break
        sys.stdout.write(nextline)
        sys.stdout.flush()

    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)

3
İfischer en ve tokland kodu oldukça iyi çalışıyor birleştirme (Değişiklikten zorunda print line,için sys.stdout.write(nextline); sys.stdout.flush(). Her iki satır yazdırmak istiyorum, Aksi Sonra tekrar, bu yüzden başka belki bir şey oluyordu, ipython en Notebook arayüzünü kullanıyor -. Açıkça çağıran, bağımsız flush()eserler.
eacousineau

3
bayım sen benim hayat kurtarıcısın !! gerçekten bu tür şeyler kütüphane kendisi yerleşik değildir garip .. çünkü ben cliapp yazmak, ben anında döngüde işleyen her şeyi göstermek istiyorum .. s'rsly ..
holms

3
Bu çözüm, hem çıktı hem de hataları sürekli olarak yazdıracak şekilde değiştirilebilir mi? Şunu değiştirirsem stderr=subprocess.STDOUTiçin stderr=subprocess.PIPEve daha sonra çağrı process.stderr.readline()döngü içinden, ben bir şeye takılmış çok koşmak o belgelerinde hakkında uyarılır kilitlenmeye görünmektedir subprocessmodülü.
davidrmcharles

7
@DavidCharles Aradığınızı düşünüyorum stdout=subprocess.PIPE,stderr=subprocess.STDOUTbu stderr yakalar ve inanıyorum (ama test etmedim) aynı zamanda stdin yakalar.
Andrew Martin

çıkış kodunu beklediğiniz için teşekkürler. Nasıl çalışacağını bilmiyordum
Vitaly Isaev

70

Python 3'te stdout arabelleği temizlendiğinde alt işlemin çıktısını satır satır yazdırmak için:

from subprocess import Popen, PIPE, CalledProcessError

with Popen(cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='') # process line here

if p.returncode != 0:
    raise CalledProcessError(p.returncode, p.args)

Uyarı: gerek yok p.poll()- eof ulaşıldığında döngü sona erer. Ve ihtiyacınız yok iter(p.stdout.readline, '')- okuma hatası Python 3'te düzeltildi.

Ayrıca bkz. Python: subprocess.communicate () öğesinden akış girişini okuma .


3
Bu çözüm benim için çalıştı. Yukarıda verilen kabul edilen çözüm benim için boş satırlar yazdırmaya devam etti.
Codename

3
Hemen baskı almak için sys.stdout.flush () eklemem gerekiyordu.
Codename

3
@Codename: sys.stdout.flush()üst öğeye ihtiyacınız yok - stdout bir dosyaya / boruya yeniden yönlendirilmezse satır arabelleğe alınır ve bu nedenle yazdırma linearabelleği otomatik olarak temizler. Alt öğede de ihtiyacınız yoktur sys.stdout.flush()- -ubunun yerine komut satırı seçeneğini geçirin.
jfs

1
@Codename: kullanmak istiyorsanız >çalıştırın python -u your-script.py > some-file. Uyarı: -uYukarıda bahsettiğim seçenek (kullanmaya gerek yok sys.stdout.flush()).
jfs

1
@mvidelgauz aramaya gerek yok p.wait()- withbloktan çıkışta çağrılır . Kullanın p.returncode.
jfs

8

Aslında çıktıyı yazdırmak istediğinizde bunu yapmanın gerçekten basit bir yolu var :

import subprocess
import sys

def execute(command):
    subprocess.check_call(command, stdout=sys.stdout, stderr=subprocess.STDOUT)

Burada sadece alt süreci kendi stdout'umuza işaret ediyoruz ve mevcut başarılı veya istisna api'yi kullanıyoruz.


1
Bu çözüm, Python 3.6 için @ tokland'ın çözümünden daha basit ve daha temizdir. Shell = True argümanının gerekli olmadığını fark ettim.
İyi İrade

İyi yakaladın, İyi İrade. Kaldırıldıshell=True
Andrew Ring

Çok zahmetli ve az kod ile mükemmel çalışır. Belki de alt işlem stderr'i sys.stderr'e yönlendirmelisiniz?
Manu

Manu kesinlikle yapabilirsin. Buraya gelmedim, çünkü sorudaki girişim stderr'ı stdout'a yönlendiriyordu.
Andrew Ring

Sys.stdout ve subprocess.STDOUT arasındaki farkı açıklayabilir misiniz?
Ron Serruya

7

@tokland

kodunuzu denedim ve 3.4 ve windows için düzeltti dir.cmd cmd dosyası olarak kaydedilen basit bir dir komutudur

import subprocess
c = "dir.cmd"

def execute(command):
    popen = subprocess.Popen(command, stdout=subprocess.PIPE,bufsize=1)
    lines_iterator = iter(popen.stdout.readline, b"")
    while popen.poll() is None:
        for line in lines_iterator:
            nline = line.rstrip()
            print(nline.decode("latin"), end = "\r\n",flush =True) # yield line

execute(c)

3
Eğer olabilir kodunuzu basitleştirmek . iter()ve end='\r\n'gereksizdir. Python varsayılan olarak evrensel satırsonu modunu kullanır, yani herhangi '\n'biri '\r\n'yazdırma sırasında çevrilir . 'latin'muhtemelen yanlış bir kodlamadır, universal_newlines=TruePython 3'te metin çıktısı almak için kullanabilirsiniz (yerel ayarın tercih edilen kodlaması kullanılarak kod çözme). Üzerinde durma .poll(), okunmamış veri yoktur tamponlu olabilir. Python betiği bir konsolda çalışıyorsa çıktısı satır arabelleğe alınır; -useçeneği kullanarak satır arabelleğe almayı zorlayabilirsiniz - flush=Trueburada ihtiyacınız yoktur .
jfs

5

Birisi her iki stdoutve stderraynı zamanda iş parçacığı kullanarak okumak istiyorsa , ben geldi budur:

import threading
import subprocess
import Queue

class AsyncLineReader(threading.Thread):
    def __init__(self, fd, outputQueue):
        threading.Thread.__init__(self)

        assert isinstance(outputQueue, Queue.Queue)
        assert callable(fd.readline)

        self.fd = fd
        self.outputQueue = outputQueue

    def run(self):
        map(self.outputQueue.put, iter(self.fd.readline, ''))

    def eof(self):
        return not self.is_alive() and self.outputQueue.empty()

    @classmethod
    def getForFd(cls, fd, start=True):
        queue = Queue.Queue()
        reader = cls(fd, queue)

        if start:
            reader.start()

        return reader, queue


process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdoutReader, stdoutQueue) = AsyncLineReader.getForFd(process.stdout)
(stderrReader, stderrQueue) = AsyncLineReader.getForFd(process.stderr)

# Keep checking queues until there is no more output.
while not stdoutReader.eof() or not stderrReader.eof():
   # Process all available lines from the stdout Queue.
   while not stdoutQueue.empty():
       line = stdoutQueue.get()
       print 'Received stdout: ' + repr(line)

       # Do stuff with stdout line.

   # Process all available lines from the stderr Queue.
   while not stderrQueue.empty():
       line = stderrQueue.get()
       print 'Received stderr: ' + repr(line)

       # Do stuff with stderr line.

   # Sleep for a short time to avoid excessive CPU use while waiting for data.
   sleep(0.05)

print "Waiting for async readers to finish..."
stdoutReader.join()
stderrReader.join()

# Close subprocess' file descriptors.
process.stdout.close()
process.stderr.close()

print "Waiting for process to exit..."
returnCode = process.wait()

if returnCode != 0:
   raise subprocess.CalledProcessError(returnCode, command)

Bunu paylaşmak istedim, çünkü bu soruyu benzer bir şey yapmaya çalıştım, ancak cevapların hiçbiri sorunumu çözmedi. Umarım birine yardımcı olur!

Benim kullanım durumumda, harici bir sürecin yaptığımız işlemi öldürdüğünü unutmayın Popen().


1
Python2 için bunun gibi bir şey kullanmak zorunda kaldım. Böyle bir şey python2'de sağlanmış olsa da, böyle bir şey kesinlikle iyi değildir.
Stuart Axon

4

Python> = 3.5'te subprocess.runbenim için çalışıyor:

import subprocess

cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, shell=True)

(yürütme sırasında çıktı alma da olmadan çalışır shell=True) https://docs.python.org/3/library/subprocess.html#subprocess.run


2
Bu "yürütme sırasında" değildir. subprocess.run()Altişlem bitmiş çalışan olduğunda arama yalnızca döndürür.
tripleee

1
Nasıl "infaz sırasında" olmadığını açıklayabilir misiniz? Bunun gibi bir şey >>> import subprocess; subprocess.run('top')de "yürütme sırasında" yazdırılıyor gibi görünüyor (ve üst kısım hiç bitmiyor). Belki bazı ince farkları kavrayamıyorum?
user7017793

Çıkışı Python'a yeniden yönlendirirseniz, örneğin stdout=subprocess.PIPEyalnızca topbitirdikten sonra okuyabilirsiniz . Python programınız alt işlemin yürütülmesi sırasında engellendi.
tripleee

1
Doğru, bu mantıklı. runYalnızca ilgilenen eğer yöntemi hala çalışır görünce o Oluşturulan olarak çıktı. Python'da çıktı ile eşzamansız olarak bir şey yapmak istiyorsanız, bunun işe yaramadığını haklıyorsunuz.
user7017793

3

Python'un stdout'unu arabelleğe aldığını ve bu nedenle stdout'u görmek biraz zaman alabilir bir Python script notundan stdout'u almak için bu sorunun yanıtlarını deneyen herkes için.

Bu, hedef betiğe her stdout yazıldıktan sonra aşağıdakiler eklenerek düzeltilebilir:

sys.stdout.flush()

1
Ancak Python'u Python'un bir alt süreci olarak çalıştırmak ilk etapta çılgınca. Betiğiniz basitçe importdiğer betiği içermelidir ; içine bakmak multiprocessingya threadingsen parallelized yürütme gerekiyorsa.
tripleee

3
@triplee Python'u Python'un bir alt süreci olarak çalıştırmanın uygun olduğu birkaç senaryo vardır. Her gün sıralı olarak çalıştırmak istediğim bir dizi python toplu komut dosyası var. Bunlar, yürütmeyi başlatan bir ana Python betiği tarafından düzenlenebilir ve alt komut dosyası başarısız olursa bana e-posta gönderir. Her komut dosyası diğerinden korumalı olarak yerleştirilmiştir - adlandırma çakışması yoktur. Paralellik yapmıyorum, bu nedenle çok işlemli ve diş açmayla ilgili değil.
user1379351

Diğer python programını, ana python programının çalıştığından farklı bir python çalıştırılabilir kullanarak da başlatabilirsiniz, örneğin,subprocess.run("/path/to/python/executable", "pythonProgramToRun.py")
Kyle Bridenstine

3

Orijinal soruyu cevaplamak için, IMO'nun alt süreci stdoutdoğrudan programınızın yönlendirmesine yönlendirmektir stdout(isteğe bağlı stderrolarak, aşağıdaki örnekte olduğu gibi aynı şey yapılabilir )

p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()

3
Hiçbir şey belirtmemek stdoutve stderraynı şeyi daha az kodla yapmak. Herhalde açık bir şekilde örtük olmaktan daha iyidir.
tripleee

1

Bu PoC sürekli olarak bir işlemin çıktısını okur ve gerektiğinde erişilebilir. Sadece son sonuç tutulur, diğer tüm çıktılar atılır, bu nedenle BORU'nun bellekte büyümesini önler:

import subprocess
import time
import threading
import Queue


class FlushPipe(object):
    def __init__(self):
        self.command = ['python', './print_date.py']
        self.process = None
        self.process_output = Queue.LifoQueue(0)
        self.capture_output = threading.Thread(target=self.output_reader)

    def output_reader(self):
        for line in iter(self.process.stdout.readline, b''):
            self.process_output.put_nowait(line)

    def start_process(self):
        self.process = subprocess.Popen(self.command,
                                        stdout=subprocess.PIPE)
        self.capture_output.start()

    def get_output_for_processing(self):
        line = self.process_output.get()
        print ">>>" + line


if __name__ == "__main__":
    flush_pipe = FlushPipe()
    flush_pipe.start_process()

    now = time.time()
    while time.time() - now < 10:
        flush_pipe.get_output_for_processing()
        time.sleep(2.5)

    flush_pipe.capture_output.join(timeout=0.001)
    flush_pipe.process.kill()

print_date.py

#!/usr/bin/env python
import time

if __name__ == "__main__":
    while True:
        print str(time.time())
        time.sleep(0.01)

Çıktı: Aralarında hiçbir şeyin ~ 2.5s aralıktan çıktı olduğunu açıkça görebilirsiniz.

>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01

0

Bu en azından Python'da çalışır3.4

import subprocess

process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
    print(line.decode().strip())

1
Bu işlemin çalışması bitene kadar döngü içinde engelleme sorunu var.
tripleee

0

Buradaki cevapların hiçbiri tüm ihtiyaçlarımı karşılamadı.

  1. Stdout için iş parçacığı yok (sıra yok, vb.)
  2. Başka şeyler olup olmadığını kontrol etmem gerektiğinden engelleme yok
  3. Akış çıktısı, bir günlük dosyasına yazma ve çıktının dize kopyasını döndürmek gibi birden çok şey yapmak için gerektiği gibi PIPE kullanın.

Küçük bir arka plan: Ben iş parçacığı havuzu yönetmek için bir ThreadPoolExecutor kullanıyorum, her biri bir alt işlem başlatmak ve onları eşzamanlılık çalışan. (Python2.7'de, ancak bu daha yeni 3.x sürümlerinde de çalışmalıdır). Ben iş parçacığı sadece çıkış toplama için mümkün olduğunca çok başka şeyler için mümkün olduğu gibi kullanmak istemiyorum (20 işlem bir havuz sadece çalıştırmak için 40 iş parçacıkları kullanıyor olacaktır; 1 işlem iş parçacığı için ve 1 stdout için ... ve stderr istiyorsanız daha fazla sanırım)

Ben bir çok istisna ve böyle burada sıyırma bu yüzden bu üretimde çalışan kod dayanmaktadır . Umarım kopyalayıp yapıştırmamı bozmadım. Ayrıca, geribildirim çok hoş geldiniz!

import time
import fcntl
import subprocess
import time

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

# Make stdout non-blocking when using read/readline
proc_stdout = proc.stdout
fl = fcntl.fcntl(proc_stdout, fcntl.F_GETFL)
fcntl.fcntl(proc_stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)

def handle_stdout(proc_stream, my_buffer, echo_streams=True, log_file=None):
    """A little inline function to handle the stdout business. """
    # fcntl makes readline non-blocking so it raises an IOError when empty
    try:
        for s in iter(proc_stream.readline, ''):   # replace '' with b'' for Python 3
            my_buffer.append(s)

            if echo_streams:
                sys.stdout.write(s)

            if log_file:
                log_file.write(s)
    except IOError:
        pass

# The main loop while subprocess is running
stdout_parts = []
while proc.poll() is None:
    handle_stdout(proc_stdout, stdout_parts)

    # ...Check for other things here...
    # For example, check a multiprocessor.Value('b') to proc.kill()

    time.sleep(0.01)

# Not sure if this is needed, but run it again just to be sure we got it all?
handle_stdout(proc_stdout, stdout_parts)

stdout_str = "".join(stdout_parts)  # Just to demo

Burada ekli yük var eminim ama benim durumumda bir endişe değildir. İşlevsel olarak ihtiyacım olanı yapar. Çözemediğim tek şey, bunun neden günlük iletileri için mükemmel çalıştığı, ancak bazı printiletilerin daha sonra ve bir kerede ortaya çıktığını görüyorum .


-2

Python 3.6'da bunu kullandım:

import subprocess

cmd = "command"
output = subprocess.call(cmd, shell=True)
print(process)

1
Bu, bu sorunun cevabı değil. Alt işlemin çıktısını almadan önce tamamlanmasını beklemek, OP'nin kaçınılmaya çalıştığı şeydir. Eski eski işlev subprocess.call(), daha yeni işlevlerle düzeltilen bazı siğillere sahiptir; Python 3.6'da genellikle bunun için kullanırsınız subprocess.run(); kolaylık sağlamak için, eski sarma işlevi subprocess.check_output()de hala kullanılabilir - işlemden gerçek çıktıyı döndürür (bu kod yalnızca çıkış kodunu döndürür, ancak daha sonra bunun yerine tanımsız bir şey yazdırır).
üçlü
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.