output langsung dari perintah subproses

186

Saya menggunakan skrip python sebagai driver untuk kode hidrodinamika. Ketika tiba saatnya untuk menjalankan simulasi, saya menggunakan subprocess.Popenuntuk menjalankan kode, mengumpulkan output dari stdout dan stderr menjadi subprocess.PIPE--- maka saya dapat mencetak (dan menyimpan ke file log) informasi output, dan memeriksa kesalahan . Masalahnya adalah, saya tidak tahu bagaimana kode ini berkembang. Jika saya menjalankannya langsung dari baris perintah, itu memberi saya output tentang apa iterasi di, jam berapa, apa langkah waktu berikutnya, dll.

Apakah ada cara untuk menyimpan output (untuk logging dan pengecekan error), dan juga menghasilkan output live-streaming?

Bagian yang relevan dari kode saya:

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)

Awalnya saya memipis run_commandmelalui teesehingga salinan langsung ke file log, dan aliran masih output langsung ke terminal - tetapi dengan cara itu saya tidak dapat menyimpan kesalahan (untuk pengetahuan saya).


Edit:

Solusi sementara:

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

kemudian, di terminal lain, jalankan tail -f log.txt(st log_file = 'log.txt').

DilithiumMatrix
sumber
1
Mungkin Anda bisa menggunakan Popen.pollseperti pada pertanyaan Stack Overflow sebelumnya .
Paulo Almeida
Beberapa perintah yang menunjukkan indikasi perkembangan (mis., git) Melakukannya hanya jika outputnya adalah "perangkat tty" (diuji melalui libc isatty()). Dalam hal ini Anda mungkin harus membuka pseudo-tty.
torek
@torek apa tty (pseudo-)?
DilithiumMatrix
2
Perangkat pada sistem mirip Unix yang memungkinkan proses berpura-pura menjadi pengguna pada port serial. Ini adalah cara kerja ssh (sisi server), misalnya. Lihat perpustakaan python pty , dan juga pexpect .
torek
Re solusi sementara: tidak perlu untuk memanggil flush, dan ada yang perlu untuk membaca dari pipa stderr jika subprocess menghasilkan banyak output stderr. Tidak ada ruang yang cukup di bidang komentar untuk menjelaskan ini ...
torek

Jawaban:

169

Anda memiliki dua cara untuk melakukan ini, baik dengan membuat iterator dari readatau readlinefungsi dan melakukan:

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)

atau

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)

Atau Anda dapat membuat readerdan writerfile. Lulus writerke Popendan baca darireader

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())

Dengan cara ini Anda akan memiliki data yang ditulis dalam test.logserta pada output standar.

Satu-satunya keuntungan dari pendekatan file adalah bahwa kode Anda tidak diblokir. Jadi Anda dapat melakukan apa pun yang Anda inginkan sementara itu dan membaca kapan pun Anda mau dari readerdalam cara yang tidak menghalangi. Ketika Anda menggunakan PIPE, readdan readlinefungsi akan memblokir sampai salah satu karakter ditulis ke pipa atau masing-masing garis ditulis ke pipa.

Viktor Kerkez
sumber
1
Ugh :-) tulis ke file, baca darinya, dan tidur di loop? Ada juga kemungkinan proses akan berakhir sebelum Anda selesai membaca file.
Guy Sirton
13
Dengan Python 3, Anda perlu iter(process.stdout.readline, b'')(yaitu sentinel yang diteruskan ke iter harus berupa string biner b'' != ''.
John Mellor
3
Untuk aliran biner, lakukan ini:for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line)
rrlamichhane
6
Menambah jawaban @JohnMellor, dengan Python 3 modifikasi berikut diperlukan: 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
tapi hasilnya tidak hidup, kan? menurut pengalaman saya, ia hanya menunggu sampai proses selesai dieksekusi dan baru kemudian mencetak ke konsol. Tautan -> stackoverflow.com/questions/30026045/…
denis631
91

Ringkasan Eksekutif (atau versi "tl; dr"): mudah saat ada paling banyak subprocess.PIPE, jika tidak sulit.

Mungkin sudah waktunya untuk menjelaskan sedikit tentang bagaimana subprocess.Popenhal itu terjadi.

(Peringatan: ini untuk Python 2.x, meskipun 3.x serupa; dan saya cukup kabur pada varian Windows. Saya mengerti hal-hal POSIX jauh lebih baik.)

The PopenFungsi perlu berurusan dengan nol-ke-tiga I / O stream, agak secara bersamaan. Ini ditandai stdin, stdoutdan stderrseperti biasa.

Anda dapat memberikan:

  • None, menunjukkan bahwa Anda tidak ingin mengarahkan aliran. Ini akan mewarisi ini seperti biasa. Perhatikan bahwa pada sistem POSIX, setidaknya, ini tidak berarti akan menggunakan Python sys.stdout, hanya stdout Python yang sebenarnya ; lihat demo di akhir.
  • Suatu intnilai. Ini adalah deskriptor file "mentah" (setidaknya dalam POSIX). (Catatan: PIPEdan STDOUTsebenarnya ints secara internal, tetapi deskriptor "tidak mungkin", -1 dan -2.)
  • Aliran — sungguh, objek apa pun dengan filenometode. Popenakan menemukan deskriptor untuk aliran itu, menggunakan stream.fileno(), dan kemudian melanjutkan sebagai intnilai.
  • subprocess.PIPE, menunjukkan bahwa Python harus membuat pipa.
  • subprocess.STDOUT( stderrhanya untuk ): beri tahu Python untuk menggunakan deskriptor yang sama seperti untuk stdout. Ini hanya masuk akal jika Anda memberikan nilai (non- None) untuk stdout, dan bahkan kemudian, itu hanya diperlukan jika Anda menetapkan stdout=subprocess.PIPE. (Kalau tidak, Anda bisa memberikan argumen yang sama dengan yang Anda berikan stdout, misalnya Popen(..., stdout=stream, stderr=stream),.)

Kasing termudah (tanpa pipa)

Jika Anda tidak mengarahkan apa pun (biarkan ketiganya sebagai nilai default Noneatau pasokan eksplisit None), Pipebuatlah itu cukup mudah. Itu hanya perlu spin off proses dan biarkan berjalan. Atau, jika Anda mengarahkan ulang ke non PIPE-—an intatau aliran- fileno()itu masih mudah, karena OS melakukan semua pekerjaan. Python hanya perlu untuk memutar subproses, menghubungkan stdin, stdout, dan / atau stderr ke deskriptor file yang disediakan.

Kasing yang masih mudah: satu pipa

Jika Anda mengarahkan hanya satu aliran, Pipemasih ada hal-hal yang cukup mudah. Mari kita memilih satu aliran pada satu waktu dan menonton.

Misalkan Anda ingin menyediakan beberapa stdin, tetapi biarkan stdoutdan stderrpergi tidak diarahkan, atau pergi ke deskriptor file. Sebagai proses induk, program Python Anda hanya perlu digunakan write()untuk mengirim data ke pipa. Anda dapat melakukannya sendiri, misalnya:

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

atau Anda dapat meneruskan data stdin ke proc.communicate(), yang kemudian melakukan yang stdin.writeditunjukkan di atas. Tidak ada output yang kembali sehingga communicate()hanya memiliki satu pekerjaan nyata: itu juga menutup pipa untuk Anda. (Jika Anda tidak menelepon proc.communicate()Anda harus menelepon proc.stdin.close()untuk menutup pipa, sehingga subproses tahu tidak ada lagi data yang masuk.)

Misalkan Anda ingin menangkap stdouttetapi pergi stdindan stderrsendirian. Sekali lagi, mudah: cukup panggil proc.stdout.read()(atau setara) sampai tidak ada lagi output. Karena proc.stdout()merupakan aliran I / O Python normal, Anda dapat menggunakan semua konstruksi normal di atasnya, seperti:

for line in proc.stdout:

atau, sekali lagi, Anda dapat menggunakan proc.communicate(), yang cukup read()untuk Anda.

Jika Anda ingin menangkap hanya stderr, itu berfungsi sama dengan stdout.

Ada satu trik lagi sebelum segalanya menjadi sulit. Misalkan Anda ingin menangkap stdout, dan juga menangkap stderrtetapi pada pipa yang sama dengan stdout:

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

Dalam hal ini, subprocess"curang"! Yah, itu harus melakukan ini, jadi itu tidak benar-benar curang: ia memulai subproses dengan stdout dan stderr yang diarahkan ke deskriptor pipa (tunggal) yang mengumpan balik ke proses induknya (Python). Di sisi induk, hanya ada lagi descriptor pipa tunggal untuk membaca output. Semua output "stderr" muncul di proc.stdout, dan jika Anda memanggil proc.communicate(), hasil stderr (nilai kedua dalam tuple) adalah None, bukan string.

Kasing keras: dua atau lebih pipa

Masalahnya semua muncul ketika Anda ingin menggunakan setidaknya dua pipa. Bahkan, subprocesskode itu sendiri memiliki bit ini:

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:

Tapi, sayangnya, di sini kita telah membuat setidaknya dua, dan mungkin tiga, pipa yang berbeda, jadi count(None)pengembaliannya 1 atau 0. Kita harus melakukan hal-hal dengan cara yang sulit.

Pada Windows, ini digunakan threading.Threaduntuk mengumpulkan hasil untuk self.stdoutdan self.stderr, dan memiliki utas induk mengirimkan self.stdindata input (dan kemudian menutup pipa).

Pada POSIX, ini digunakan polljika tersedia, jika tidak select, untuk mengakumulasi output dan mengirimkan input stdin. Semua ini berjalan di (induk) proses / utas.

Utas atau jajak pendapat / pilih diperlukan di sini untuk menghindari kebuntuan. Misalkan, misalnya, bahwa kami telah mengalihkan ketiga aliran ke tiga pipa terpisah. Misalkan lebih lanjut bahwa ada batasan kecil pada seberapa banyak data dapat dimasukkan ke dalam pipa sebelum proses penulisan ditunda, menunggu proses pembacaan untuk "membersihkan" pipa dari ujung yang lain. Mari kita atur batas kecil itu menjadi satu byte, hanya untuk ilustrasi. (Ini sebenarnya cara kerja, kecuali bahwa batasnya jauh lebih besar dari satu byte.)

Jika orang tua (Python) proses mencoba untuk menulis beberapa byte-katakanlah, 'go\n'untuk proc.stdin, byte pertama masuk dan kemudian yang kedua menyebabkan proses Python untuk menangguhkan, menunggu subproses untuk membaca byte pertama, mengosongkan pipa.

Sementara itu, misalkan subproses memutuskan untuk mencetak ramah "Halo! Jangan Panik!" salam. The Hmasuk ke pipa stdout, tetapi yang emenyebabkan untuk menangguhkan, menunggu induknya untuk membaca bahwa H, mengosongkan pipa stdout.

Sekarang kita terjebak: proses Python tertidur, menunggu untuk selesai mengatakan "pergi", dan subproses juga tertidur, menunggu untuk selesai mengatakan "Halo! Jangan Panik!".

The subprocess.Popenkode menghindari masalah ini dengan threading-atau-pilih / jajak pendapat. Ketika byte bisa melewati pipa, mereka pergi. Ketika mereka tidak bisa, hanya utas (bukan keseluruhan proses) yang harus tidur — atau, dalam kasus pilih / jajak pendapat, proses Python menunggu secara bersamaan untuk "dapat menulis" atau "data tersedia", menulis ke stdin proses hanya ketika ada ruang, dan membaca stdout dan / atau stderr hanya ketika data siap. The proc.communicate()kode (sebenarnya _communicatedi mana kasus berbulu ditangani) kembali setelah semua data stdin (jika ada) telah dikirim dan semua data stdout dan / atau stderr telah terakumulasi.

Jika Anda ingin membaca keduanya stdoutdan stderrpada dua pipa yang berbeda (terlepas dari stdinpengalihan apa pun ), Anda juga harus menghindari jalan buntu. Skenario kebuntuan di sini berbeda — itu terjadi ketika subproses menulis sesuatu yang lama stderrsaat Anda menarik data dari stdout, atau sebaliknya — tetapi itu masih ada.


Demo

Saya berjanji untuk menunjukkan bahwa, tidak diarahkan, Python subprocesses menulis ke stdout yang mendasarinya, bukan sys.stdout. Jadi, ini beberapa kode:

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()

Ketika dijalankan:

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

start show2
hello

Perhatikan bahwa rutin pertama akan gagal jika Anda menambahkan stdout=sys.stdout, karena StringIOobjek tidak memiliki fileno. Yang kedua akan menghilangkan hellojika Anda menambahkan stdout=sys.stdoutsejak sys.stdoutitu telah dialihkan ke os.devnull.

(Jika Anda mengarahkan file-deskriptor-1 Python, subproses akan mengikuti pengalihan itu. open(os.devnull, 'w')Panggilan menghasilkan aliran yang fileno()lebih besar dari 2.)

torek
sumber
Hmm. Demo Anda tampaknya menunjukkan kebalikan dari klaim pada akhirnya. Anda mengarahkan ulang stdout Python ke buffer tetapi stdout subproses masih menuju ke konsol. Bagaimana itu bermanfaat? Apakah saya melewatkan sesuatu?
Guy Sirton
@GuySirton: demo menunjukkan bahwa stdout subprocess (ketika tidak diarahkan secara eksplisit ke sys.stdout) pergi ke stdout Python , bukan stdout program python ( sys.). Yang saya akui adalah ... perbedaan aneh. Apakah ada cara yang lebih baik untuk mengatakan ini?
torek
itu bagus untuk diketahui tetapi kami benar-benar ingin menangkap output subproses di sini jadi mengubah sys.stdout itu keren tapi tidak membantu kami. Pengamatan yang baik yang berkomunikasi harus menggunakan sesuatu seperti select (), polling, atau utas.
Guy Sirton
Saya telah menambahkan implementasi dengan select ()
sivann
20

Kita juga dapat menggunakan iterator file default untuk membaca stdout daripada menggunakan iter construct dengan readline ().

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
    sys.stdout.write(line)
Jughead
sumber
Jawaban paling elegan di sini!
Nir
9
Solusi ini tidak ditampilkan secara real time. Itu menunggu sampai proses selesai dan menampilkan semua output sekaligus. Dalam solusi Viktor Kerkez, jika "your_command" ditampilkan secara progresif, output akan mengikuti secara progresif, selama "your_command" menyiram stdout dari waktu ke waktu (karena pipa).
Eric H.
1
@Nir karena tidak hidup.
melMass
Solusi ini berulang pada deskriptor default, jadi itu hanya akan memperbarui ketika garis memperbarui dalam output. Untuk pembaruan berbasis karakter, Anda perlu mengulangi metode read () seperti yang ditunjukkan dalam solusi Viktor. Tapi itu berlebihan untuk kasus penggunaan saya.
Jughead
11

Jika Anda dapat menggunakan perpustakaan pihak ketiga, Anda mungkin dapat menggunakan sesuatu seperti sarge(pengungkapan: Saya adalah pengelolanya). Pustaka ini memungkinkan akses non-pemblokiran ke aliran keluaran dari subproses - itu berlapis di atas subprocessmodul.

Vinay Sajip
sumber
Kerja bagus di sarge, BTW. Itu memang memecahkan persyaratan OP, tetapi mungkin agak berat untuk kasus penggunaan itu.
deepelement
Jika Anda menyarankan alat setidaknya tunjukkan contoh penggunaan untuk kasus persis ini.
Serhiy
4

Solusi 1: Masuk stdoutDAN stderrsecara bersamaan dalam waktu nyata

Sebuah solusi sederhana yang mencatat stdout DAN stderr secara bersamaan, baris demi baris secara realtime ke dalam file log.

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()

Solusi 2: Suatu fungsi read_popen_pipes()yang memungkinkan Anda untuk beralih ke kedua pipa (stdout / stderr), secara bersamaan dalam waktu nyata

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()
Rotareti
sumber
3

Solusi yang bagus tapi "kelas berat" adalah menggunakan Twisted - lihat bagian bawah.

Jika Anda mau hidup dengan hanya stdout sesuatu di sepanjang garis itu akan bekerja:

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

(Jika Anda menggunakan read () ia mencoba membaca seluruh "file" yang tidak berguna, yang benar-benar bisa kami gunakan di sini adalah sesuatu yang membaca semua data yang ada di dalam pipa sekarang)

Seseorang mungkin juga mencoba untuk mendekati ini dengan threading, misalnya:

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

Sekarang kita berpotensi menambahkan stderr juga dengan memiliki dua utas.

Namun perlu dicatat bahwa dokumen subproses tidak menyarankan untuk menggunakan file-file ini secara langsung dan merekomendasikan untuk menggunakan communicate()(sebagian besar berkaitan dengan deadlock yang menurut saya bukan masalah di atas) dan solusinya sedikit klunky sehingga benar-benar seperti modul subproses tidak cukup untuk pekerjaan (juga lihat: http://www.python.org/dev/peps/pep-3145/ ) dan kita perlu melihat sesuatu yang lain.

Solusi yang lebih terlibat adalah dengan menggunakan Twisted seperti yang ditunjukkan di sini: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html

Cara Anda melakukan ini dengan Twisted adalah membuat proses menggunakan reactor.spawnprocess()dan menyediakan ProcessProtocolyang kemudian memproses output secara tidak sinkron. Kode Python sampel Twisted ada di sini: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py

Guy Sirton
sumber
Terima kasih! Saya baru saja mencoba sesuatu seperti ini (berdasarkan komentar @PauloAlmeida, tetapi panggilan saya ke subprocess.Popen memblokir - yaitu hanya datang ke loop sementara setelah kembali ...
DilithiumMatrix
1
Bukan itu yang terjadi. Ini memasuki loop sementara segera kemudian memblokir read()panggilan sampai subproses keluar dan proses induk menerima EOFpada pipa.
Alp
@Alp menarik! begitulah.
DilithiumMatrix
Ya, saya terlalu cepat memposting ini. Ini sebenarnya tidak berfungsi dengan baik dan tidak mudah diperbaiki. kembali ke meja gambar.
Guy Sirton
1
@zhermes: Jadi masalah dengan read () adalah ia akan mencoba membaca seluruh output sampai EOF yang tidak berguna. readline () membantu dan mungkin semua yang Anda butuhkan (garis yang sangat panjang juga bisa menjadi masalah). Anda juga harus waspada terhadap penyangga dalam proses yang Anda luncurkan ...
Guy Sirton
3

Selain semua jawaban ini, satu pendekatan sederhana juga bisa sebagai berikut:

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

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

    if not line:
        break

    print(line.strip())

Ulangi aliran yang dapat dibaca selama itu dapat dibaca dan jika hasilnya kosong, hentikan.

Kuncinya di sini adalah readline()mengembalikan baris (dengan \ndi bagian akhir) selama ada output dan kosong jika itu benar-benar di bagian akhir.

Semoga ini bisa membantu seseorang.

kabirbaidhya
sumber
3

Berdasarkan semua hal di atas saya sarankan versi yang sedikit dimodifikasi (python3):

  • sementara loop menelepon readline (Solusi iter yang disarankan tampaknya memblokir selamanya untuk saya - Python 3, Windows 7)
  • terstruktur sehingga penanganan data yang dibaca tidak perlu diduplikasi setelah polling kembali tidak-None
  • stderr disalurkan ke stdout sehingga kedua output output dibaca
  • Kode ditambahkan untuk mendapatkan nilai keluar dari cmd.

Kode:

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
mrtnlrsn
sumber
Bagian returncodeitu sangat penting dalam kasus saya.
stardust
2

Sepertinya output buffer-line akan bekerja untuk Anda, dalam hal ini sesuatu seperti berikut ini mungkin cocok. (Peringatan: belum teruji.) Ini hanya akan memberikan stdout subproses secara real time. Jika Anda ingin memiliki stderr dan stdout secara real time, Anda harus melakukan sesuatu yang lebih rumit 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...
Puncak gunung
sumber
2

Mengapa tidak diatur stdoutlangsung ke sys.stdout? Dan jika Anda perlu menampilkan log juga, maka Anda cukup mengganti metode tulis f.

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)
xaav
sumber
Itu tidak akan berfungsi: modul subproses garpu dan menetapkan stdoutfile descriptor ke file descriptor dari objek file yang dikirimkan. Metode menulis tidak akan pernah dipanggil (setidaknya itulah yang dilakukan subproses untuk stderr, saya pikir itu sama untuk stdout).
t.animal
2

Semua solusi di atas saya coba gagal untuk memisahkan stderr dan stdout output, (beberapa pipa) atau diblokir selamanya ketika buffer pipa OS penuh yang terjadi ketika perintah Anda menjalankan output terlalu cepat (ada peringatan untuk ini pada python polling () manual subproses). Satu-satunya cara yang dapat diandalkan yang saya temukan adalah melalui select, tetapi ini adalah solusi posix-only:

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()
sivann
sumber
Alternatif lain adalah dengan memintal satu utas per pipa. Setiap thread dapat memblokir I / O pada pipa, tanpa memblokir thread lainnya. Tapi ini memperkenalkan serangkaian masalahnya sendiri. Semua metode memiliki gangguan, Anda hanya memilih yang mana yang menurut Anda paling tidak mengganggu. :-)
torek
2

Mirip dengan jawaban sebelumnya tetapi solusi berikut ini bekerja untuk saya di windows menggunakan Python3 untuk menyediakan metode umum untuk mencetak dan masuk secara realtime ( mendapatkan-realtime-output-menggunakan-python ):

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()
scottysbasement
sumber
2

Saya pikir subprocess.communicatemetode ini agak menyesatkan: sebenarnya mengisi stdout dan stderr yang Anda tentukan di subprocess.Popen.

Namun, membaca dari subprocess.PIPEyang Anda dapat memberikan kepada subprocess.Popen's stdout dan stderr parameter akhirnya akan mengisi buffer pipa OS dan kebuntuan aplikasi Anda (terutama jika Anda sudah beberapa proses / thread yang harus menggunakan subprocess).

Solusi yang saya usulkan adalah menyediakan file dengan stdout dan stderr - dan membaca konten file alih-alih membaca dari jalan buntu PIPE. File-file ini dapat tempfile.NamedTemporaryFile()- yang juga dapat diakses untuk dibaca saat sedang ditulis oleh subprocess.communicate.

Di bawah ini adalah contoh penggunaan:

        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

Dan ini adalah kode sumber yang siap digunakan dengan sebanyak mungkin komentar yang bisa saya berikan untuk menjelaskan apa yang dilakukannya:

Jika Anda menggunakan python 2, pastikan untuk menginstal versi terbaru dari paket subprocess32 dari pypi.


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


Victor Klapholz
sumber
1

Ini adalah kelas yang saya gunakan di salah satu proyek saya. Ini mengalihkan output dari subproses ke log. Pada awalnya saya mencoba hanya menimpa metode tulis tapi itu tidak berhasil karena subproses tidak akan pernah menyebutnya (pengalihan terjadi pada tingkat yang diajukan). Jadi saya menggunakan pipa saya sendiri, mirip dengan bagaimana hal itu dilakukan dalam subprocess-module. Ini memiliki keuntungan merangkum semua log logging / pencetakan dalam adaptor dan Anda dapat dengan mudah memberikan contoh logger ke 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())

Jika Anda tidak perlu login tetapi hanya ingin menggunakan, print()Anda dapat dengan jelas menghapus sebagian besar kode dan membuat kelas lebih pendek. Anda juga bisa memperluas oleh __enter__dan __exit__metode dan panggilan finisheddi __exit__sehingga Anda bisa dengan mudah menggunakannya sebagai konteks.

t.hewan
sumber
1

Tidak ada solusi Pythonic yang bekerja untuk saya. Ternyata proc.stdout.read()atau sejenisnya dapat memblokir selamanya.

Karena itu, saya menggunakan teeseperti ini:

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

Solusi ini nyaman jika Anda sudah menggunakan shell=True.

${PIPESTATUS}menangkap status keberhasilan seluruh rantai perintah (hanya tersedia di Bash). Jika saya menghapusnya && exit ${PIPESTATUS}, maka ini akan selalu mengembalikan nol karena teetidak pernah gagal.

unbuffermungkin diperlukan untuk mencetak setiap baris segera ke terminal, daripada menunggu terlalu lama sampai "penyangga pipa" terisi. Namun, unbuffer menelan status keluar dari pernyataan (SIG Abort) ...

2>&1 juga mencatat stderror ke file.

Mike76
sumber