सबप्रोसेस का उपयोग करके रियलटाइम आउटपुट प्राप्त करना


135

मैं एक कमांड लाइन प्रोग्राम (svnadmin सत्यापित) के लिए एक आवरण स्क्रिप्ट लिखने की कोशिश कर रहा हूं जो ऑपरेशन के लिए एक अच्छा प्रगति संकेतक प्रदर्शित करेगा। यह मुझे आउटपुट के रूप में जल्द ही लिपटे प्रोग्राम से आउटपुट की प्रत्येक पंक्ति को देखने में सक्षम होने की आवश्यकता है।

मुझे लगा कि मैं इस कार्यक्रम का उपयोग subprocess.Popen, उपयोग stdout=PIPE, फिर प्रत्येक पंक्ति को पढ़ूंगा क्योंकि यह आया था और उसी के अनुसार कार्य करता है। हालाँकि, जब मैंने निम्नलिखित कोड चलाया, तो आउटपुट कहीं-कहीं बफ़र करने लगा, जिससे यह 332 के माध्यम से दो हिस्सों, 1 पंक्ति में दिखाई दिया, फिर 439 (आउटपुट की अंतिम पंक्ति) के माध्यम से 333

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', '')

उपप्रकारों पर दस्तावेज़ को थोड़ा देखने के बाद, मैंने bufsizeपैरामीटर की खोज की Popen, इसलिए मैंने 1 (बफर प्रत्येक पंक्ति) और 0 (कोई बफर) के लिए bufsize सेट करने का प्रयास किया, लेकिन लाइनों को वितरित किए जाने के तरीके को बदलने के लिए न तो मूल्य प्रतीत होता है।

इस बिंदु पर मैं तिनके के लिए पकड़ना शुरू कर रहा था, इसलिए मैंने निम्नलिखित आउटपुट लूप लिखा:

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

लेकिन एक ही परिणाम मिला।

क्या सबप्रोसेस का उपयोग करके निष्पादित प्रोग्राम का 'रियलटाइम' प्रोग्राम आउटपुट प्राप्त करना संभव है? क्या पायथन में कुछ अन्य विकल्प है जो आगे-संगत (नहीं exec*) है?


1
क्या आपने sydout=PIPEमूल प्रक्रिया को दरकिनार करते हुए उपप्रोसेस को सीधे अपने कंसोल पर लिखने की कोशिश की है?
एस.लॉट

5
बात यह है कि मैं आउटपुट पढ़ना चाहता हूं। यदि यह सीधे कंसोल पर आउटपुट होता है, तो मैं यह कैसे कर सकता हूं? इसके अलावा, मैं नहीं चाहता कि उपयोगकर्ता लिपटे हुए प्रोग्राम से आउटपुट देखें, बस मेरा आउटपुट।
क्रिस लीब

फिर "वास्तविक समय" का प्रदर्शन क्यों? मुझे उपयोग का मामला नहीं मिला।
एस.लॉट

8
शेल का उपयोग न करें = सत्य। यह आपके खोल को बेकार कर देता है। इसके बजाय p = Popen (['' svnadmin ',' verify ',' / var / svn / repos / config '], stdout = PIPE, stderr = STDOUT) का उपयोग करें
nosklo

2
@ S.Lott मूल रूप से, svnadmin सत्यापित किए गए हर संशोधन के लिए आउटपुट की एक रेखा प्रिंट करता है। मैं एक अच्छा प्रगति संकेतक बनाना चाहता था जो अत्यधिक मात्रा में उत्पादन का कारण नहीं होगा। उदाहरण के लिए, तरह तरह की भूल
क्रिस लाइब

जवाबों:


82

मैंने यह कोशिश की, और कोड के कारण किसी कारण के लिए

for line in p.stdout:
  ...

बफ़र आक्रामक, संस्करण

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

नहीं करता। जाहिरा तौर पर यह एक ज्ञात बग है: http://bugs.python.org/issue3907 (यह मुद्दा अब "बंद" 29 अगस्त 2018 तक है)


यह पुराने पायथन IO कार्यान्वयन में एकमात्र गड़बड़ नहीं है। यही कारण है कि Py2.6 और Py3k पूरी तरह से नए IO पुस्तकालय के साथ समाप्त हो गए।
टिम लिन

3
यदि सबप्रोसेस खाली लाइन देता है तो यह कोड टूट जाएगा। एक बेहतर समाधान के while p.poll() is Noneबजाय का उपयोग करना होगा while True, और if not line
Decuma

6
@exhuma: यह ठीक काम करता है। रीडलाइन एक खाली रेखा पर "\ n" लौटाती है, जो सही के रूप में मूल्यांकन नहीं करता है। पाइप बंद होने पर यह केवल एक खाली स्ट्रिंग लौटाता है, जो तब होगा जब उपप्रकार समाप्त हो जाएगा।
ऐलिस परसेल

1
@ भविष्य में रेफ के लिए: py2 + में utf-8 लाइनों को प्रिंट करें print(line.decode('utf-8').rstrip())
जोनाथन कोमर

3
साथ ही प्रक्रिया के आउटपुट का वास्तविक रियलटाइम पढ़ने के लिए आपको अजगर को बताना होगा कि आप कोई बफरिंग नहीं चाहते हैं। प्रिय अजगर बस मुझे सीधे आउटपुट दें। और यहां बताया गया है: आपको पर्यावरण चर सेट करने की आवश्यकता है PYTHONUNBUFFERED=1। यह विशेष रूप से आउटपुट के लिए उपयोगी है जो अनंत हैं
जॉर्ज प्लिगोपोपोलोस

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
@ क्योंकि शायद p.stdout.close()अस्पष्ट है।
अनातोली टेकटोनिक

1
/: @nbro शायद इसलिए है क्योंकि कोड ... कोई स्पष्टीकरण के साथ दिया गया था
हारून हॉल

3
इस b '' के बारे में क्या है?
मैनुअलसेलीन 3r

29

आप सीधे उप-प्रॉडक्शन आउटपुट को स्ट्रीम में डायरेक्ट कर सकते हैं। सरलीकृत उदाहरण:

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

क्या यह आपको इस तथ्य के बाद भी सामग्री प्राप्त करने की अनुमति देता है .communicate()? या माता-पिता stderr / stdout धाराओं के लिए सामग्री खो गए हैं?
theferrit32

नहीं, communicate()लौटी कोई विधि नहीं CompletedProcess। इसके अलावा, capture_outputपारस्परिक रूप से अनन्य है stdoutऔर stderr
ऐडन फेल्डमैन

20

आप यह कोशिश कर सकते हैं:

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

यदि आप पढ़ने के बजाय रीडलाइन का उपयोग करते हैं, तो कुछ मामले होंगे जहां इनपुट संदेश मुद्रित नहीं होता है। एक कमांड के साथ इसे इनलाइन इनपुट की आवश्यकता होती है और अपने लिए देखें।


हां, रीडलाइन () का उपयोग करके छपाई बंद कर दी जाएगी (यहां तक ​​कि sys.stdout.flush () कॉल करने के साथ)
मार्क मा

3
क्या यह अनिश्चित काल तक लटका हुआ है? मैं एक उप-समाधान चाहूंगा जिसमें प्रारंभिक सबप्रोसेस होने पर लूप को संपादित करने के लिए बॉयलरप्लेट कोड भी शामिल हो। क्षमा करें, मुझे इसमें कोई फर्क नहीं पड़ता कि मैं कितनी बार देखता हूं, सबप्रोसेस वगैरह एक ऐसी चीज है जिसे मैं कभी काम नहीं कर सकता।
थोरसुमोनर

1
पायथन में जब '' का परीक्षण किया जाता है तो हम उपयोग नहीं कर सकते हैं?
ग्रेग बेल

2
यह लंबे समय तक चलने वाली नौकरियों के लिए सबसे अच्छा समाधान है। लेकिन इसका उपयोग नहीं करना चाहिए और कोई नहीं! = कोई नहीं। आप का उपयोग नहीं करना चाहिए!
कैरी

क्या stderr भी इससे प्रदर्शित होता है?
पीटर वोगेलेर

7

स्ट्रीमिंग उपप्रक्रिया stdin और stdout के साथ अजगर में asyncio द्वारा ब्लॉग पोस्ट केविन मैकार्थी से पता चलता है कि कैसे asyncio साथ यह करने के लिए:

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

यह पोस्ट करने के लिए एक मामूली संशोधन के साथ काम करता है
Jeef

हाय @Jeef आप तय कर सकते हैं तो मैं जवाब अद्यतन कर सकते हैं?
पाब्लो

नमस्ते, जो मेरे लिए काम करता था लेकिन मुझे कुछ त्रुटि संदेशों से छुटकारा पाने के लिए निम्नलिखित जोड़ना पड़ा: import nest_asyncio; nest_asyncio.apply()और process = await create_subprocess_shell(*command, stdout=PIPE, stderr=PIPE, shell=True)इसके बजाय शेल कमांड का उपयोग करना process = await create_subprocess_exec(...)। चीयर्स!
user319436 3:26

4

रियल टाइम आउटपुट इश्यू हल: मैंने पायथन में इसी तरह के मुद्दे का सामना किया, जबकि सी प्रोग्राम से रियल टाइम आउटपुट कैप्चर किया। मैंने " fflush (stdout) जोड़ा ;" मेरे सी कोड में। इसने मेरे लिए काम किया। यहाँ स्निप कोड है

<< C कार्यक्रम >>

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

<< पायथन कार्यक्रम >>

#!/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)

<< OUTPUT >> प्रिंट: काउंट 1 प्रिंट: काउंट 2 प्रिंट: काउंट 3

आशा करता हूँ की ये काम करेगा।

~ साईराम


1
यह एकमात्र ऐसी चीज थी जो वास्तव में मदद करती थी। मैंने flush(stdout)C ++ में समान कोड ( ) का उपयोग किया । धन्यवाद!
गेरहार्ड हैगर

मुझे एक अजगर स्क्रिप्ट के साथ एक ही समस्या थी कि एक उप-स्क्रिप्ट के रूप में एक और अजगर स्क्रिप्ट को कॉल करना। सबप्रोसेस प्रिंट पर, "फ्लश" आवश्यक था (अजगर 3 में प्रिंट ("हैलो", फ्लश = ट्रू))। इसके अलावा, अभी भी बहुत सारे उदाहरण हैं (2020) अजगर 2, यह अजगर 3 है, इसलिए +1
smajtkst

3

मैं वापस उसी समस्या में भाग गया। मेरा समाधान readविधि के लिए पुनरावृत्ति खोदने का था , जो कि आपके उपप्रकार समाप्त होने पर भी तुरंत वापस आ जाएगा, आदि।


3

उपयोग के मामले के आधार पर, आप उपप्रकार में बफ़रिंग को भी अक्षम करना चाह सकते हैं।

यदि सबप्रोसेस एक पायथन प्रक्रिया होगी, तो आप कॉल से पहले ऐसा कर सकते हैं:

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

या वैकल्पिक रूप से इस envतर्क में पास Popen

अन्यथा, यदि आप लिनक्स / यूनिक्स पर हैं, तो आप stdbufटूल का उपयोग कर सकते हैं । जैसे:

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

अन्य विकल्पों के बारे में भी यहां देखें stdbuf

( इसी जवाब के लिए यहां भी देखें ।)


2

मैंने इस समाधान का उपयोग सबप्रोसेस पर रियलटाइम आउटपुट प्राप्त करने के लिए किया। ब्रेक स्टेटमेंट या संभावित अनंत लूप की आवश्यकता को पूरा करते ही यह लूप बंद हो जाएगा।

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
क्या यह संभव है कि यह बिना स्टेपआउट बफर के लूप से बाहर निकल जाएगा?
जयाज

मैंने एक उपयुक्त उत्तर के लिए बहुत कुछ देखा है जो पूरा होने पर लटका नहीं था! मुझे इसके if out=='': breakबाद समाधान के रूप में मिलाout = sub_process...
SOS

2

मिले इस "प्लग-एंड-प्ले" समारोह यहां । एक जादू की तरह काम किया!

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
stderr=subprocess.STDOUTस्ट्रीमिंग डेटा को कैप्चर करने में वास्तव में जोड़ने से बहुत मदद मिलती है। मैं इसे उभार रहा हूं।
खान

1
यहाँ का मुख्य गोमांस स्वीकृत उत्तर
ट्रिपल

2

आप उपप्रकार के उत्पादन में प्रत्येक बाइट पर एक पुनरावृत्ति का उपयोग कर सकते हैं। यह इनप्रोवन अपडेट (उप-प्रक्रम से पिछली आउटपुट लाइन को ओवरराइट करने के साथ समाप्त होने वाली लाइनें) देता है:

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

पायथन 3.x में यह प्रक्रिया लटक सकती है क्योंकि आउटपुट एक स्ट्रिंग के बजाय बाइट सरणी है। सुनिश्चित करें कि आप इसे एक स्ट्रिंग में डिकोड करते हैं।

Python 3.6 से शुरू करके आप इसे Popen Constructorencoding में पैरामीटर का उपयोग करके कर सकते हैं । पूरा उदाहरण:

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)

ध्यान दें कि यह कोड आउटपुट त्रुटियों को रीडायरेक्ट और संभालता हैstderrstdout


1

गैर-अवरोधक रीडलाइन के साथ pexpect [ http://www.noah.org/wiki/Pexpect ] का उपयोग करने से यह समस्या दूर हो जाएगी। यह इस तथ्य से उपजा है कि पाइप बफर कर रहे हैं, और इसलिए आपके ऐप का आउटपुट पाइप से बफर हो रहा है, इसलिए आप उस आउटपुट तक नहीं पहुंच सकते जब तक कि बफर भर जाता है या प्रक्रिया मर जाती है।


0

पूर्ण समाधान:

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
के बाद से आप उपयोग कर रहे universal_newlines=Trueपर Popen()विकल्प के पूरे मुद्दे है कि - कॉल, तो आप शायद में भी उनमें से अपनी खुद की हैंडलिंग डाल करने के लिए की जरूरत नहीं है।
मार्टिउ

1
यह अनावश्यक जटिल लगता है। यह बफ़रिंग समस्याओं को हल नहीं करता है। मेरे उत्तर में लिंक देखें ।
jfs

यह एकमात्र तरीका है जिससे मैं realtime (- outbuf = L) में rsync प्रगति आउटपुट प्राप्त कर सकता हूं! धन्यवाद
Mohammadhzp

0

यह मूल कंकाल है जो मैं हमेशा इसके लिए उपयोग करता हूं। इससे टाइमआउट लागू करना आसान हो जाता है और अपरिहार्य हैंगिंग प्रक्रियाओं से निपटने में सक्षम हो जाता है।

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

(यह समाधान पायथन 2.7.15 के साथ परीक्षण किया गया है)
आपको बस प्रत्येक पंक्ति पढ़ने / लिखने के बाद sys.stdout.flush () की आवश्यकता है:

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

कुछ जवाब अजगर 3.x या pthon 2.x का सुझाव देते हैं, नीचे दिए गए कोड दोनों के लिए काम करेंगे।

 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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.