alt işlem komutundan canlı çıktı


187

Hidrodinamik kod için bir sürücü olarak bir python komut dosyası kullanıyorum. Simülasyonu çalıştırmak için zaman geldiğinde subprocess.Popen, kodu çalıştırmak, stdout ve stderr'den çıktıyı bir subprocess.PIPE--- içine toplamak için çıktı bilgisini yazdırabilirim (ve bir günlük dosyasına kaydedebilirim) ve herhangi bir hatayı kontrol edebilirim . Sorun, kodun nasıl ilerlediğine dair hiçbir fikrim yok. Doğrudan komut satırından çalıştırırsam, ne yineleme, ne zaman, bir sonraki zaman adımının ne olduğu vb.

Hem çıktıyı depolamanın (günlük kaydı ve hata denetimi için) hem de canlı akış çıktısı oluşturmanın bir yolu var mı?

Kodumun ilgili bölümü:

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

Başlangıçta ben Borulama run_commandaracılığıyla teebir kopyası log-dosyaya doğrudan gitti ki ve hala çıkış doğrudan terminali için dere - ama (benim bilgi için) ben herhangi bir hata saklamak edemez şekilde.


Düzenle:

Geçici çözüm:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
    log_file.flush()

sonra başka bir terminalde tail -f log.txt(st log_file = 'log.txt') komutunu çalıştırın .


1
Belki bir önceki Yığın Taşması sorusundakiPopen.poll gibi kullanabilirsiniz .
Paulo Almeida

İlerleme göstergesini gösteren bazı komutlar (örn. git) Yalnızca çıktıları bir "tty aygıtı" ise (libc ile test edilir isatty()) bunu yapar. Bu durumda sözde tty açmanız gerekebilir.
torek

@torek (sahte) tty nedir?
DilithiumMatrix

2
Bir işlemin seri bağlantı noktasında kullanıcı gibi davranmasına izin veren Unix benzeri sistemlerde bulunan aygıtlar. Örneğin ssh (sunucu tarafı) böyle çalışır. Bkz. Python pty kütüphanesi ve ayrıca pexpect .
torek

Geçici çözümü Re: aramaya gerek yoktur flush, ve orada olan alt süreci çok Stderr çıktı üretir eğer Stderr borusundan okumak ihtiyacı. Bir açıklama alanında bunu açıklamak için yeterli yer yok ...
torek

Yanıtlar:


169

Bunu yapmanın iki yolu vardır, ya readda readlineişlevlerinden bir yineleyici oluşturarak ve yapın:

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), ''):  # replace '' with b'' for Python 3
        sys.stdout.write(c)
        f.write(c)

veya

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for line in iter(process.stdout.readline, ''):  # replace '' with b'' for Python 3
        sys.stdout.write(line)
        f.write(line)

Veya bir readerve bir writerdosya oluşturabilirsiniz . Adresine writergeçin Popenvereader

import io
import time
import subprocess
import sys

filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

Bu şekilde test.loghem standart çıktıda hem de veriler yazılır .

Dosya yaklaşımının tek avantajı kodunuzun engellenmemesidir. Böylece bu arada ne istersen yapabilirsin ve istediğin zaman readerengellemeyen bir şekilde okuyabilirsin . Eğer kullandığınız zaman PIPE, readve readlineya bir karakter borusuna yazılır veya çizgi sırasıyla boru yazılır kadar fonksiyonları engeller.


1
Ugh :-) bir dosyaya yaz, ondan oku ve döngüde uyu? Dosyayı okumayı bitirmeden önce işlemin sona erme olasılığı da vardır.
Guy Sirton

13
Python 3 ile ihtiyacınız var iter(process.stdout.readline, b'')(yani iter'e geçen sentinel ikili bir dize olmalı b'' != ''.
John Mellor

3
İkili akışlar için şunu yapın:for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line)
rrlamichhane

6
@JohnMellor'un cevabına ek olarak, Python 3'te aşağıdaki değişikliklere ihtiyaç duyuldu: process = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, b'') sys.stdout.write(line.decode(sys.stdout.encoding))
bergercookie

4
ama çıktı canlı değil, değil mi? deneyimlerime göre, işlemin tamamlanması bitene kadar bekler ve ancak daha sonra konsola yazdırır. Bağlantı -> stackoverflow.com/questions/30026045/…
denis631

91

Yönetici Özeti (veya "tl; dr" sürümü): en fazla bir tane olduğunda kolay subprocess.PIPE, aksi takdirde zor.

İşini nasıl yaptığını biraz açıklamanın zamanı geldi subprocess.Popen.

(Dikkat: Bu Python 2.x içindir, ancak 3.x benzerdir; ve Windows varyantında oldukça bulanıkım. POSIX şeylerini çok daha iyi anlıyorum.)

PopenFonksiyon biraz aynı zamanda sıfır-üç I / O akışları ile başa çıkmak için gerekmektedir. Bunlar belirtilir stdin, stdoutve, stderrher zamanki gibi.

Şunları sağlayabilirsiniz:

  • None, akışı yeniden yönlendirmek istemediğinizi gösterir. Bunları her zamanki gibi miras alacaktır. POSIX sistemlerinde, bunun en azından Python'ları kullanacağı anlamına gelmediğini sys.stdout, sadece Python'un gerçek stdout'unu; sonunda demoya bakın.
  • Bir intdeğer. Bu bir "ham" dosya tanımlayıcıdır (en azından POSIX'te). (Yan not: PIPEve STDOUTaslında intdahili olarak, ancak -1 ve -2 "imkansız" tanımlayıcılar.)
  • Bir akış — gerçekten, filenoyöntemi olan herhangi bir nesne . Popenbu akış için tanımlayıcıyı bulur ve stream.fileno()bir intdeğerde olduğu gibi ilerler .
  • subprocess.PIPE, Python'un bir boru oluşturması gerektiğini gösterir.
  • subprocess.STDOUT( stderryalnızca): Python'a aynı tanımlayıcıyı kullanmasını söyleyin stdout. Eğer bir (non verdiyse Bu sadece mantıklı Noneiçin) değerini stdout, ve o zaman bile, sadece bir ihtiyaç ayarladığınız takdirde stdout=subprocess.PIPE. (Aksi takdirde, sağladığınız aynı argümanı sağlayabilirsiniz stdout, örneğin Popen(..., stdout=stream, stderr=stream).)

En kolay durumlar (boru yok)

Hiçbir şeyi yönlendirmezseniz (her üçünü de varsayılan Nonedeğer veya sarf malzemesi açık olarak bırakın None), Pipeoldukça kolaydır. Sadece alt süreci kapatmalı ve çalışmasına izin vermeli. Bir olmayan yönlendirmek Ya da, PIPE-bir intveya dere var fileno()OS tüm çalışmaları yaptığı gibi -it, kolay hala. Python, yalnızca stdin, stdout ve / veya stderr'sini sağlanan dosya tanımlayıcılarına bağlayarak alt işlemi kapatmalıdır.

Hala kolay kasa: bir boru

Yalnızca bir akışı yeniden yönlendirirseniz, Pipeyine de oldukça kolay şeyler vardır. Bir seferde bir akış seçip izleyelim.

Bazı tedarik istediğinizi varsayalım stdin, ama izin stdoutve stderrun-yönlendirildi gitmek veya bir dosya tanımlayıcı gidin. Üst işlem olarak, Python programınızın write()verileri kanaldan göndermek için kullanması yeterlidir . Bunu kendiniz yapabilirsiniz, örneğin:

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

veya stdin verilerini proc.communicate()daha sonra stdin.writeyukarıda gösterilen şekilde aktarabilirsiniz . Geri gelen çıktı yok, bu yüzden communicate()sadece bir gerçek iş var: aynı zamanda boruyu sizin için kapatır. (Arama yapmazsanız , boruyu kapatmak için proc.communicate()aramalısınız proc.stdin.close(), böylece alt süreç daha fazla veri olmadığını bilir.)

Eğer yakalama istediğinizi varsayalım stdoutama izinli stdinve stderryalnız. Yine, kolaydır: proc.stdout.read()daha fazla çıkış kalmayıncaya kadar arayın (veya eşdeğerini). Yana proc.stdout()normal Python I / O sizin gibi, üzerinde tüm normal yapıları kullanabilir akışı:

for line in proc.stdout:

veya, tekrar kullanabilirsiniz proc.communicate(), ki bu sadece read()sizin için yapar .

Yalnızca yakalamak istiyorsanız stderr, ile aynı şekilde çalışır stdout.

İşler zorlaşmadan önce bir numara daha var. Eğer yakalama istediğinizi varsayalım stdoutve aynı zamanda yakalamak stderrancak Stdout'a aynı boruya:

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

Bu durumda, subprocess"hileler"! Bunu yapmak zorunda, bu yüzden gerçekten hile değil: hem stdout hem de stderr, üst (Python) sürecine geri beslenen (tekli) boru tanımlayıcıya yönlendirilen alt süreci başlatır. Üst tarafta yine çıktıyı okumak için yalnızca tek bir boru tanımlayıcı bulunur. Tüm "stderr" çıktıları görünür proc.stdoutve eğer ararsanız proc.communicate(), stderr sonucu (gruptaki ikinci değer) Nonedize olmaz.

Zor durumlar: iki veya daha fazla boru

En az iki boru kullanmak istediğinizde problemler ortaya çıkıyor. Aslında, subprocesskodun kendisi şu bite sahiptir:

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

Ama ne yazık ki, burada en az iki ve belki üç farklı boru yaptık, bu yüzden count(None)1 veya 0 döndürür. İşleri zor yoldan yapmalıyız.

Windows'ta, bu kullanımlar threading.Threadiçin birikir sonuçlarına self.stdoutve self.stderrve ana iş parçacığı teslim sahiptir self.stdin(daha sonra yakın ve boru) giriş verileri.

POSIX'te bu, pollmevcutsa, aksi takdirde select, çıktı biriktirmek ve stdin girişi iletmek için kullanır . Tüm bunlar (tek) üst işlem / iş parçacığında çalışır.

Kilitlenmeyi önlemek için iş parçacığı veya anket / seçim gereklidir. Örneğin, üç akışı da üç ayrı boruya yönlendirdiğimizi varsayalım. Ayrıca, yazma işlemi askıya alınmadan önce bir boruya ne kadar verinin doldurulabileceğine ilişkin küçük bir sınır olduğunu ve okuma işleminin boruyu diğer uçtan "temizlemesini" beklediğini varsayalım. Bu küçük limiti sadece gösterim amacıyla tek bir bayta ayarlayalım. (Sınırın bir bayttan çok daha büyük olması dışında, aslında işler böyle çalışır.)

Ebeveyn (Python) işlemi çalışır, byte-diyelim ki birkaç yazmaya Eğer 'go\n'üzere proc.stdin, ilk bayt gider ve daha sonra ikinci boru boşaltma, ilk bayt okumak için alt işlemi için bekleyen, askıya alma Python işlemi neden olur.

Bu arada, alt sürecin dostça bir "Merhaba! Panik Yapmayın!" selamlama. HStdout borusuna gider, ama eüst okumak için bekleyen, askıya almak neden olur Hstdout'u borusunu boşaltma.

Şimdi sıkıştık: Python süreci uyuyor, "git" diyerek bitirmeyi bekliyor ve alt süreç de uyuyor, "Merhaba! Panik yapmayın!" Demeyi bitirmeyi bekliyor.

subprocess.PopenKod parçacığı-ya-select / anket ile bu sorunu ortadan kaldırır. Baytlar boruların üzerinden geçebildiklerinde giderler. Yapamadıklarında, yalnızca bir iş parçacığı (tüm işlem değil) uyumak zorundadır - veya seçme / yoklama durumunda, Python işlemi aynı anda "yazabilir" veya "kullanılabilir veriler" için bekler, sürecin standartlarına yazar yalnızca yer olduğunda ve stdout ve / veya stderr değerini yalnızca veri hazır olduğunda okur. proc.communicate()Kodu (aslında _communicatekıllı vakalar işlendiği yerlerde) döner tüm Stdin verileri bir kez (varsa) gönderilmiş ve tüm stdout ve / veya stderr veri birikmiş edilmiştir.

Her ikisini de stdoutve stderriki farklı boruda (herhangi bir stdinyönlendirmeden bağımsız olarak ) okumak istiyorsanız, kilitlenmeden de kaçınmanız gerekir. Buradaki kilitlenme senaryosu farklıdır - alt süreç, stderrveri çekerken uzun süre bir şey yazdığında stdoutveya tam tersi olduğunda gerçekleşir - ancak hala oradadır.


Demo

Ben yönlendirilmemiş Python subprocesses temel stdout yazmak değil göstermek için söz verdi sys.stdout. İşte bazı kodlar:

from cStringIO import StringIO
import os
import subprocess
import sys

def show1():
    print 'start show1'
    save = sys.stdout
    sys.stdout = StringIO()
    print 'sys.stdout being buffered'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    in_stdout = sys.stdout.getvalue()
    sys.stdout = save
    print 'in buffer:', in_stdout

def show2():
    print 'start show2'
    save = sys.stdout
    sys.stdout = open(os.devnull, 'w')
    print 'after redirect sys.stdout'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    sys.stdout = save

show1()
show2()

Çalıştırıldığında:

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

Eklediğinizde stdout=sys.stdout, ilk rutinin başarısız olacağını unutmayın , çünkü bir StringIOnesnede yok fileno. İkincisi, yeniden yönlendirildiğinden beri helloeklerseniz , atlar .stdout=sys.stdoutsys.stdoutos.devnull

(Eğer Python'un dosya tanımlayıcısı-1 yönlendirmek ise alt süreci olacak o yönlendirmeyi izleyin. open(os.devnull, 'w')Çağrı kimin akışı üretir fileno()2. büyüktür)


Hmm. Gösteriniz sonunda iddianın tam tersini gösteriyor. Python'un stdout'unu arabelleğe yeniden yönlendiriyorsunuz, ancak alt işlem stdoutu hala konsola gidiyor. Bu nasıl faydalı? Bir şey mi kaçırıyorum?
Guy Sirton

@GuySirton: (açıkça yönlendirilmiyorum altişlem stdout'u o demo gösterileri sys.stdout) gider Python'un Stdout'a değil, piton programı 'ın ( sys.) stdout'u. Kabul ettiğim ... garip bir ayrım. Bunu ifade etmenin daha iyi bir yolu var mı?
torek

bunu bilmek güzel ama burada alt süreç çıktısını yakalamak istiyoruz. İletişim sağlayan iyi gözlem, select (), anket veya iş parçacıkları gibi bir şey kullanıyor olmalıdır.
Guy Sirton


Select ()
sivann

20

Varsayılan dosya yineleyicisini, readline () ile iter yapısını kullanmak yerine stdout'u okumak için de kullanabiliriz.

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
    sys.stdout.write(line)

Buradaki en şık cevap!
Nir

9
Bu çözüm gerçek zamanlı olarak görüntülenmez. İşlem tamamlanana kadar bekler ve tüm çıktıları bir kerede görüntüler. Viktor Kerkez'in çözümünde, "your_command" aşamalı olarak görüntüleniyorsa, "your_command" zaman zaman (borudan dolayı) stdoutu temizlediği sürece çıktı aşamalı olarak izler.
Eric H.

1
@Nir çünkü canlı değil.
melMass

Bu çözüm varsayılan tanımlayıcıda yinelenir, bu nedenle yalnızca çıktıda bir satır güncellendiğinde güncellenir. Karakter tabanlı bir güncelleme için Viktor'un çözümünde gösterildiği gibi read () yöntemini tekrarlamanız gerekir. Ama bu benim kullanım durumum için aşırıya kaçmıştı.
Jughead

11

Üçüncü taraf kitaplıkları kullanabiliyorsanız, şöyle bir şey kullanabilirsiniz sarge(açıklama: Ben onun koruyucusuyum). Bu kütüphane, alt işlemlerden çıkış akışlarına engellemesiz erişime izin verir - subprocessmodül üzerinde katmanlıdır .


Çavuş, BTW üzerinde iyi çalışmalar. Bu gerçekten OP'nin gereksinimini çözüyor, ancak bu kullanım durumu için biraz ağır olabilir.
deepelement

Bir araç öneriyorsanız, en azından bu durum için bir kullanım örneği gösterin.
Serhiy

4

Giriş: Çözüm 1 stdoutVE stderrzamanlı olarak aynı anda

Hem stdout hem de stderr'i aynı anda, satır satır gerçek zamanlı olarak bir günlük dosyasına kaydeden basit bir çözüm .

import subprocess as sp
from concurrent.futures import ThreadPoolExecutor


def log_popen_pipe(p, stdfile):

    with open("mylog.txt", "w") as f:

        while p.poll() is None:
            f.write(stdfile.readline())
            f.flush()

        # Write the rest from the buffer
        f.write(stdfile.read())


with sp.Popen(["ls"], stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    with ThreadPoolExecutor(2) as pool:
        r1 = pool.submit(log_popen_pipe, p, p.stdout)
        r2 = pool.submit(log_popen_pipe, p, p.stderr)
        r1.result()
        r2.result()

Çözüm 2: Her read_popen_pipes()iki boru (stdout / stderr) üzerinde eşzamanlı olarak gerçek zamanlı olarak yineleme yapmanızı sağlayan bir işlev

import subprocess as sp
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()
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

# The function in use:

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):
        print(out_line, end='')
        print(err_line, end='')

    return p.poll()

3

İyi ama "ağır" bir çözüm Twisted kullanmaktır - aşağıya bakın.

Sadece stdout ile yaşamaya istekliyseniz, bu çizgiler boyunca bir şey işe yaramalıdır:

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
   stdoutdata = popenobj.stdout.readline()
   if stdoutdata:
      sys.stdout.write(stdoutdata)
   else:
      break
print "Return code", popenobj.returncode

(Eğer okuma () kullanırsanız yararlı olmayan "dosya" nın tamamını okumaya çalışır, burada gerçekten kullanabileceğimiz şey şu anda kanalda bulunan tüm verileri okuyan bir şeydir)

Ayrıca buna diş çekme ile yaklaşmaya çalışılabilir, örneğin:

import subprocess
import sys
import threading

popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)

def stdoutprocess(o):
   while True:
      stdoutdata = o.stdout.readline()
      if stdoutdata:
         sys.stdout.write(stdoutdata)
      else:
         break

t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

Şimdi potansiyel olarak iki iplik geçirerek stderr de ekleyebiliriz.

Bununla birlikte, alt işlem dokümanları bu dosyaları doğrudan kullanmayla cesaretini yitirir ve kullanılmasını önerir communicate()(çoğunlukla yukarıdaki bir sorun olmadığını düşündüğüm kilitlenmelerle ilgilidir) ve çözümler biraz klunky'dir, bu yüzden alt işlem modülü oldukça fazla değildir. iş (ayrıca bkz: http://www.python.org/dev/peps/pep-3145/ ) ve başka bir şeye bakmamız gerekir.

Daha karmaşık bir çözüm, burada gösterildiği gibi Twisted kullanmaktır : https://twistedmatrix.com/documents/11.1.0/core/howto/process.html

Twisted ile bunu yapmanın yolu , daha sonra çıktıyı eşzamansız olarak işleyen reactor.spawnprocess()bir kullanarak ve sağlayarak işleminizi oluşturmaktır ProcessProtocol. Twisted örnek Python kodu burada: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py


Teşekkürler! Ben sadece böyle bir şey denedim (@PauloAlmeida'nın yorumuna dayanarak, ama benim alt işlem çağrısı.Popen engelleme - yani sadece bir kez döndüğünde while döngüsüne geliyor ...
DilithiumMatrix

1
Olanlar bu değil. While döngüsüne hemen giriyor, ardından read()alt işlem çıkana ve üst süreç EOFboruya alınana kadar çağrıyı engelliyor .
Alp

@Alp ilginç! İşte bu.
DilithiumMatrix

Evet, bunu göndermek için çok hızlıydım. Aslında düzgün çalışmıyor ve kolayca düzeltilemiyor. çizim tablosuna geri dönelim.
Guy Sirton

1
@zhermes: Bu nedenle read () ile ilgili sorun, yararlı olmayan EOF'a kadar tüm çıktıyı okumaya çalışmasıdır. readline () yardımcı olur ve ihtiyacınız olan her şey olabilir (gerçekten uzun çizgiler de bir sorun olabilir). Ayrıca başlattığınız süreçte tamponlamaya dikkat etmeniz gerekiyor ...
Guy Sirton

3

Tüm bu cevaba ek olarak, basit bir yaklaşım da aşağıdaki gibi olabilir:

process = subprocess.Popen(your_command, stdout=subprocess.PIPE)

while process.stdout.readable():
    line = process.stdout.readline()

    if not line:
        break

    print(line.strip())

Okunabilir olduğu sürece okunabilir akışta döngü yapın ve boş bir sonuç alırsa durdurun.

Buradaki anahtar, readline()bir satır (\n , gerçekten sonunda bir çıktı ve boş olduğu sürece sonunda) .

Umarım bu birine yardımcı olur.


3

Yukarıdakilerin hepsine dayanarak biraz değiştirilmiş bir versiyon (python3) öneririm:

  • while loop calling readline (Önerilen iter çözümü benim için sonsuza dek engellenmiş gibi görünüyordu - Python 3, Windows 7)
  • Anket geri dönüşleri yapılmadığında, okunan verilerin işlenmesinin çoğaltılmasına gerek yoktur.None
  • stderr her iki çıkış çıkışının da okunması için stdout'a bağlanır
  • Cmd çıkış değerini almak için kod eklendi.

Kod:

import subprocess
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT, universal_newlines=True)
while True:
    rd = proc.stdout.readline()
    print(rd, end='')  # and whatever you want to do...
    if not rd:  # EOF
        returncode = proc.poll()
        if returncode is not None:
            break
        time.sleep(0.1)  # cmd closed stdout, but not exited yet

# You may want to check on ReturnCode here

returncodeBenim durumumda bu kısım çok önemliydi.
yıldız tozu

2

Hat tamponlu çıktı sizin için çalışacak gibi görünüyor, bu durumda aşağıdakine benzer bir şey uygun olabilir. (Uyarı: test edilmemiştir.) Bu, yalnızca alt işlemin stdout'unu gerçek zamanlı olarak verecektir. Gerçek zamanlı olarak hem stderr hem de stdout'a sahip olmak istiyorsanız, daha karmaşık bir şey yapmanız gerekecek.select .

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
    line = proc.stdout.readline()
    print line
    log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
    print line
    log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...

2

Neden stdoutdoğrudan ayarlanmadı sys.stdout? Bir günlüğe de çıktı almanız gerekiyorsa, f'nin yazma yöntemini geçersiz kılabilirsiniz.

import sys
import subprocess

class SuperFile(open.__class__):

    def write(self, data):
        sys.stdout.write(data)
        super(SuperFile, self).write(data)

f = SuperFile("log.txt","w+")       
process = subprocess.Popen(command, stdout=f, stderr=f)

Bu işe yaramaz: alt işlem modülü stdout, dosya tanımlayıcısını iletilen dosya nesnesinin dosya tanımlayıcısına çatallar ve ayarlar . Yazma yöntemi asla çağrılmaz (en azından alt işlem stderr için bunu yapar, stdout için aynı olduğunu tahmin ediyorum).
t.animal

2

Denediğim yukarıdaki çözümlerin tümü, stderr ve stdout çıkışını ayırmayı başaramadı (birden fazla boru) veya OS boru tamponu dolduğunda sonsuza kadar bloke edildi; anket () alt işlem kılavuzu). Bulduğum tek güvenilir yol seçme yoluyla oldu, ancak bu sadece posix bir çözüm:

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    poller = select.poll()
    poller.register(p.stdout, select.POLLIN)
    poller.register(p.stderr, select.POLLIN)

    coutput=''
    cerror=''
    fdhup={}
    fdhup[p.stdout.fileno()]=0
    fdhup[p.stderr.fileno()]=0
    while sum(fdhup.values()) < len(fdhup):
        try:
            r = poller.poll(1)
        except select.error, err:
            if err.args[0] != EINTR:
                raise
            r=[]
        for fd, flags in r:
            if flags & (select.POLLIN | select.POLLPRI):
                c = os.read(fd, 1024)
                if rtoutput:
                    sys.stdout.write(c)
                    sys.stdout.flush()
                if fd == p.stderr.fileno():
                    cerror+=c
                else:
                    coutput+=c
            else:
                fdhup[fd]=1
    return p.poll(), coutput.strip(), cerror.strip()

Başka bir alternatif, boru başına bir ipliği döndürmektir. Her bir diş, diğer diş (ler) i bloke etmeden boru üzerinde bloke edici I / O yapabilir. Ancak bu kendi sorunlarını ortaya koyuyor. Tüm yöntemlerin sıkıntıları vardır, sadece hangisini en az sinir bozucu bulduğunuzu seçersiniz. :-)
torek

2

Önceki yanıtlara benzer ancak aşağıdaki çözüm, Python3'ü kullanarak gerçek zamanlı olarak yazdırmak ve oturum açmak için ortak bir yöntem sağlamak için benim için çalıştı ( gerçek zamanlı çıktı-kullanarak-python almak ):

def print_and_log(command, logFile):
    with open(logFile, 'wb') as f:
        command = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)

        while True:
            output = command.stdout.readline()
            if not output and command.poll() is not None:
                f.close()
                break
            if output:
                f.write(output)
                print(str(output.strip(), 'utf-8'), flush=True)
        return command.poll()

2

Ben subprocess.communicateyöntem biraz yanıltıcı olduğunu düşünüyorum : aslında belirttiğiniz stdout ve stderr doldurur subprocess.Popen.

Yine de, 'ın stdout ve stderr parametrelerine subprocess.PIPEsağlayabileceğiniz şeylerden okumak , sonunda OS boru tamponlarını dolduracak ve uygulamanızı kilitleyecektir (özellikle kullanmanız gereken birden fazla işlem / iş parçacığınız varsa ).subprocess.Popensubprocess

Benim önerdiğim çözüm stdout ve stderr'a dosya sağlamak ve kilitlenmeden okumak yerine dosyaların içeriğini okumak PIPE. Bu dosyalar tempfile.NamedTemporaryFile()- tarafından yazılırken okumak için erişilebilir subprocess.communicate.

Aşağıda örnek bir kullanım yer almaktadır:

        try:
            with ProcessRunner(('python', 'task.py'), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

Ve bu, ne yaptığını açıklamak için sağlayabileceğim kadar çok yorumla kullanılmaya hazır olan kaynak kodudur :

Python 2 kullanıyorsanız, lütfen önce pypi'den subprocess32 paketinin en son sürümünü yüklediğinizden emin olun .


import os
import sys
import threading
import time
import tempfile
import logging

if os.name == 'posix' and sys.version_info[0] < 3:
    # Support python 2
    import subprocess32 as subprocess
else:
    # Get latest and greatest from python 3
    import subprocess

logger = logging.getLogger(__name__)


class ProcessError(Exception):
    """Base exception for errors related to running the process"""


class ProcessTimeout(ProcessError):
    """Error that will be raised when the process execution will exceed a timeout"""


class ProcessRunner(object):
    def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
        """
        Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
        Process Runner. This is a class that should be used as a context manager - and that provides an iterator
        for reading captured output from subprocess.communicate in near realtime.

        Example usage:


        try:
            with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

        :param args: same as subprocess.Popen
        :param env: same as subprocess.Popen
        :param timeout: same as subprocess.communicate
        :param bufsize: same as subprocess.Popen
        :param seconds_to_wait: time to wait between each readline from the temporary file
        :param kwargs: same as subprocess.Popen
        """
        self._seconds_to_wait = seconds_to_wait
        self._process_has_timed_out = False
        self._timeout = timeout
        self._process_done = False
        self._std_file_handle = tempfile.NamedTemporaryFile()
        self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
                                         stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
        self._thread = threading.Thread(target=self._run_process)
        self._thread.daemon = True

    def __enter__(self):
        self._thread.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._thread.join()
        self._std_file_handle.close()

    def __iter__(self):
        # read all output from stdout file that subprocess.communicate fills
        with open(self._std_file_handle.name, 'r') as stdout:
            # while process is alive, keep reading data
            while not self._process_done:
                out = stdout.readline()
                out_without_trailing_whitespaces = out.rstrip()
                if out_without_trailing_whitespaces:
                    # yield stdout data without trailing \n
                    yield out_without_trailing_whitespaces
                else:
                    # if there is nothing to read, then please wait a tiny little bit
                    time.sleep(self._seconds_to_wait)

            # this is a hack: terraform seems to write to buffer after process has finished
            out = stdout.read()
            if out:
                yield out

        if self._process_has_timed_out:
            raise ProcessTimeout('Process has timed out')

        if self._process.returncode != 0:
            raise ProcessError('Process has failed')

    def _run_process(self):
        try:
            # Start gathering information (stdout and stderr) from the opened process
            self._process.communicate(timeout=self._timeout)
            # Graceful termination of the opened process
            self._process.terminate()
        except subprocess.TimeoutExpired:
            self._process_has_timed_out = True
            # Force termination of the opened process
            self._process.kill()

        self._process_done = True

    @property
    def return_code(self):
        return self._process.returncode



1

İşte projelerimden birinde kullandığım bir sınıf. Bir alt işlemin çıktısını günlüğe yeniden yönlendirir. İlk başta sadece yazma yönteminin üzerine yazmayı denedim, ancak alt işlem asla çağırmayacağı için işe yaramıyor (dosyalama tanımlayıcı düzeyinde yeniden yönlendirme gerçekleşiyor). Bu yüzden, alt işlem modülünde nasıl yapıldığına benzer şekilde kendi pipomu kullanıyorum. Bu, adaptördeki tüm günlük / yazdırma mantığını kapsülleme avantajına sahiptir ve kaydedicinin örneklerini aşağıdakilere aktarabilirsiniz Popen:subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))

class LogAdapter(threading.Thread):

    def __init__(self, logname, level = logging.INFO):
        super().__init__()
        self.log = logging.getLogger(logname)
        self.readpipe, self.writepipe = os.pipe()

        logFunctions = {
            logging.DEBUG: self.log.debug,
            logging.INFO: self.log.info,
            logging.WARN: self.log.warn,
            logging.ERROR: self.log.warn,
        }

        try:
            self.logFunction = logFunctions[level]
        except KeyError:
            self.logFunction = self.log.info

    def fileno(self):
        #when fileno is called this indicates the subprocess is about to fork => start thread
        self.start()
        return self.writepipe

    def finished(self):
       """If the write-filedescriptor is not closed this thread will
       prevent the whole program from exiting. You can use this method
       to clean up after the subprocess has terminated."""
       os.close(self.writepipe)

    def run(self):
        inputFile = os.fdopen(self.readpipe)

        while True:
            line = inputFile.readline()

            if len(line) == 0:
                #no new data was added
                break

            self.logFunction(line.strip())

Günlüğe kaydetmeye ihtiyacınız yoksa ancak yalnızca kullanmak print()istiyorsanız, kodun büyük bölümlerini kaldırabilir ve sınıfı daha kısa tutabilirsiniz. Ayrıca, bir tarafından genişletmek olabilir __enter__ve __exit__yönteme ve çağrı finishediçinde __exit__kolayca bağlam olarak kullanabilirsiniz diye.


1

Pythonic çözümlerinin hiçbiri benim için çalışmadı. Öyle proc.stdout.read()ya da benzeri bir şeyin sonsuza dek engellenebileceği ortaya çıktı .

Bu nedenle, teeböyle kullanın :

subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', shell=True, check=True, executable='/bin/bash')

Zaten kullanıyorsanız bu çözüm kullanışlıdır shell=True.

${PIPESTATUS}tüm komut zincirinin başarı durumunu yakalar (yalnızca Bash'te bulunur). Eğer atlanırsam && exit ${PIPESTATUS}, o zaman teeasla sıfır olmaz çünkü asla başarısız olmaz.

unbuffer"boru tamponu" dolana kadar çok beklemek yerine her satırı hemen terminale yazdırmak için gerekli olabilir. Ancak, arabelleğe alma, onaylayıcının (SIG Abort) çıkış durumunu yutar ...

2>&1 ayrıca stderror'u dosyaya kaydeder.

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.