सबप्रोसेस कमांड से लाइव आउटपुट


186

मैं हाइड्रोडायनामिक्स कोड के लिए एक ड्राइवर के रूप में एक अजगर स्क्रिप्ट का उपयोग कर रहा हूं। जब सिमुलेशन चलाने का समय आता है, तो मैं subprocess.Popenकोड को चलाने के लिए उपयोग करता हूं , आउटपुट को stdout और stderr से एक subprocess.PIPE--- में इकट्ठा करता हूं, फिर मैं आउटपुट जानकारी प्रिंट कर सकता हूं (और लॉग-फाइल में सहेज सकता हूं) और किसी भी त्रुटि के लिए जांच कर सकता हूं। । समस्या यह है, मुझे पता नहीं है कि कोड कैसे प्रगति कर रहा है। अगर मैं इसे सीधे कमांड लाइन से चलाता हूं, तो यह मुझे आउटपुट देता है कि इसकी पुनरावृत्ति क्या है, किस समय, अगली बार क्या है, आदि।

क्या आउटपुट (लॉगिंग और त्रुटि जाँच के लिए) दोनों को स्टोर करने का एक तरीका है, और एक लाइव-स्ट्रीमिंग आउटपुट भी है?

मेरे कोड का प्रासंगिक अनुभाग:

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)

मूल रूप से मैं इसके run_commandमाध्यम से पाइपिंग कर रहा था teeताकि एक कॉपी सीधे लॉग-फाइल पर जाए, और स्ट्रीम अभी भी टर्मिनल पर सीधे आउटपुट हो - लेकिन इस तरह मैं किसी भी त्रुटि को संग्रहीत नहीं कर सकता (मेरे ज्ञान को)।


संपादित करें:

अस्थायी समाधान:

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

फिर, दूसरे टर्मिनल में, रन tail -f log.txt(सेंट log_file = 'log.txt')।


1
शायद आप पिछले स्टैक ओवरफ्लो प्रश्न केPopen.poll रूप में उपयोग कर सकते हैं ।
पाउलो अल्मेडा

कुछ कमांड जो प्रगति के संकेत दिखाते हैं (उदाहरण के लिए git) ऐसा तभी करते हैं जब उनका आउटपुट एक "ट्टी डिवाइस" (लिबेक के माध्यम से परीक्षण isatty()) हो। उस स्थिति में आपको एक छद्म टेट खोलना पड़ सकता है।
torek

@torek क्या है (छद्म-) tty?
Dil लिथियमMatrix

2
यूनिक्स जैसी प्रणाली पर उपकरण जो एक प्रक्रिया को एक सीरियल पोर्ट पर उपयोगकर्ता होने का नाटक करने की अनुमति देते हैं। उदाहरण के लिए, यह ssh (सर्वर साइड) कैसे काम करता है। अजगर pty पुस्तकालय देखें , और pexpect भी ।
torek

पुन अस्थायी समाधान: वहाँ फोन करने की आवश्यकता नहीं flushहै, और वहाँ है stderr पाइप से पढ़ने के लिए अगर उपप्रक्रिया ज्यादा stderr उत्पादन का उत्पादन की जरूरत है। यह समझाने के लिए एक टिप्पणी क्षेत्र में पर्याप्त जगह नहीं है ...
torek

जवाबों:


169

आपके पास ऐसा करने के दो तरीके हैं, या तो कार्यों readया readlineकार्यों से एक इटरेटर बनाकर :

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)

या

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)

या आप readerएक writerफ़ाइल बना सकते हैं । पास writerकरने के लिए Popenऔर से पढ़ेंreader

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

इस तरह आपके पास डेटा लिखा होगा test.log मानक आउटपुट पर और साथ ही ।

फ़ाइल दृष्टिकोण का एकमात्र लाभ यह है कि आपका कोड ब्लॉक नहीं होता है। इसलिए आप इस दौरान जो चाहें कर सकते हैं और जब भी आप readerगैर-अवरुद्ध तरीके से चाहते हैं तो पढ़ सकते हैं । जब आप उपयोग करते हैं PIPE, readऔर readlineफ़ंक्शन तब तक ब्लॉक होंगे जब तक या तो एक चरित्र पाइप को नहीं लिखा जाता है या पाइप को एक पंक्ति क्रमशः लिखी जाती है।


1
ऊग :-) एक फाइल पर लिखिए, उसमें से पढ़िए और लूप में सो जाइए? फ़ाइल पढ़ने के समाप्त होने से पहले प्रक्रिया समाप्त होने का एक मौका भी है।
गाय सिरटन

13
अजगर 3 के साथ, आप की जरूरत iter(process.stdout.readline, b'')(यानी प्रहरी के लिए पारित आईटीईआर एक द्विआधारी स्ट्रिंग होने के लिए, की जरूरत है के बाद से b'' != ''
जॉन मेलर

3
बाइनरी धाराओं के लिए, यह करें:for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line)
rrlamichhane

6
PyJon 3 में @JohnMellor के उत्तर को जोड़ने के लिए निम्नलिखित संशोधनों की आवश्यकता थी: 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
लेकिन आउटपुट लाइव नहीं है, है ना? मेरे अनुभव में, यह तब तक इंतजार करता है जब तक कि प्रक्रिया निष्पादित नहीं हो जाती है और केवल कंसोल पर प्रिंट होता है। लिंक -> stackoverflow.com/questions/30026045/…
denis631

91

कार्यकारी सारांश (या "tl; डॉ।" संस्करण): यह आसान है जब वहाँ सबसे अधिक है subprocess.PIPE, अन्यथा यह कठिन है।

यह इस बारे में थोड़ा समझाने का समय हो सकता है कि subprocess.Popenयह कैसे काम करता है।

(कैविएट: यह पाइथन 2.x के लिए है, हालाँकि 3.x समान है; और मैं विंडोज वेरिएंट पर काफी फजी हूं। मुझे पॉसिक्स का सामान ज्यादा बेहतर लगता है।)

Popenसमारोह शून्य करने वाली तीन आई / ओ धाराओं, कुछ हद तक एक साथ के साथ सौदा करने की जरूरत है। ये चिह्नित हैं stdin, stdoutऔरstderr हमेशा की तरह।

आप प्रदान कर सकते हैं:

  • None, यह दर्शाता है कि आप स्ट्रीम को पुनर्निर्देशित नहीं करना चाहते हैं। यह इनकी बजाय हमेशा की तरह इनहेरिट करेगा। ध्यान दें कि POSIX सिस्टम पर, कम से कम, इसका मतलब यह नहीं है कि यह पायथन का उपयोग करेगा sys.stdout, बस पायथन का वास्तविक स्टडआउट; अंत में डेमो देखें।
  • एक intमान। यह एक "कच्ची" फ़ाइल डिस्क्रिप्टर (POSIX में कम से कम) है। (साइड नोट: PIPEऔर STDOUTवास्तव में intआंतरिक रूप से हैं, लेकिन "असंभव" वर्णनकर्ता, -1 और -2 हैं।)
  • एक धारा - वास्तव में, एक filenoविधि के साथ कोई भी वस्तु । Popenउस स्ट्रीम के लिए डिस्क्रिप्टर ढूंढेगा, उपयोग कर सकता है stream.fileno(), और फिर intमान के लिए आगे बढ़ सकता है ।
  • subprocess.PIPE, यह दर्शाता है कि पायथन को एक पाइप बनाना चाहिए।
  • subprocess.STDOUT( stderrकेवल के लिए): पायथन को उसी विवरणक के रूप में उपयोग करने के लिए कहें stdout। यह केवल तभी समझ में आता है जब आपने (गैर None) मूल्य प्रदान किया हो stdout, और तब भी, यदि आप सेट करते हैं , तो इसकी आवश्यकता हैstdout=subprocess.PIPE । (अन्यथा आप केवल वही तर्क प्रदान कर सकते हैं, जो आप के लिए प्रदान करते हैं stdout, उदाहरण के लिए Popen(..., stdout=stream, stderr=stream)।)

सबसे आसान मामले (कोई पाइप नहीं)

यदि आप कुछ भी नहीं अनुप्रेषित करते हैं (सभी तीनों को डिफ़ॉल्ट Noneमान या आपूर्ति को स्पष्ट छोड़ दें None), तो Pipeयह काफी आसान है। यह सिर्फ उपप्रजाति को स्पिन करने और इसे चलाने की जरूरत है। या, यदि आप एक गैर- या एक धारा के लिए पुनर्निर्देशित करते हैं PIPE- यह अभी भी आसान है, क्योंकि OS सभी काम करता है। पायथन को उप-प्रजाति को बंद करने की जरूरत है, जो उसके स्टड, स्टडआउट और / या स्टेंडर को उपलब्ध फाइल डिस्क्रिप्टर से जोड़ता है।intfileno()

अभी भी आसान मामला: एक पाइप

यदि आप केवल एक धारा को पुनर्निर्देशित करते हैं, तब Pipeभी चीजें बहुत आसान हैं। चलो एक समय में एक स्ट्रीम चुनें और देखें।

आप कुछ की आपूर्ति करना चाहते हैं मान लीजिए stdin, लेकिन जाने stdoutऔर stderrअन-निर्देशित कर दिये जाते हैं, या एक फ़ाइल वर्णनकर्ता में जाते हैं। मूल प्रक्रिया के रूप में, आपके पायथन कार्यक्रम को बस write()पाइप के नीचे डेटा भेजने के लिए उपयोग करने की आवश्यकता होती है । आप इसे स्वयं कर सकते हैं, जैसे:

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

या आप स्टड डेटा को पास कर सकते हैं proc.communicate(), जो तब stdin.writeऊपर दिखाया गया है। कोई आउटपुट नहीं आ रहा है इसलिए communicate()केवल एक और वास्तविक काम है: यह आपके लिए पाइप को भी बंद कर देता है। (यदि आप कॉल नहीं करते हैं तो proc.communicate()आपको proc.stdin.close()पाइप को बंद करने के लिए कॉल करना होगा , ताकि उपप्रोसेस को पता चल सके कि कोई अधिक डेटा नहीं है।)

माना कि आप कब्जा करना चाहते हैं stdoutलेकिन छोड़ दें stdinऔर stderrअकेले। फिर से, यह आसान है: बस कॉल proc.stdout.read()(या समतुल्य) जब तक कि अधिक आउटपुट न हो। चूंकि proc.stdout()एक सामान्य अजगर I / O धारा है, आप इस पर सभी सामान्य निर्माणों का उपयोग कर सकते हैं, जैसे:

for line in proc.stdout:

या, फिर से, आप उपयोग कर सकते हैं proc.communicate(), जो बस read()आपके लिए करता है ।

यदि आप केवल कब्जा करना चाहते हैं stderr, तो यह उसी के साथ काम करता है stdout

इससे पहले कि मुश्किल हो, एक और तरकीब है। मान लें कि आप कब्जा करना चाहते हैं stdout, और कब्जा भी करते हैं stderrलेकिन stdout के समान पाइप पर:

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

इस मामले में, subprocess"धोखा"! ठीक है, इसे ऐसा करना है, इसलिए यह वास्तव में धोखा नहीं है: यह उपप्रकार को अपने स्टडआउट और इसके स्टडर के साथ (एकल) पाइप-डिस्क्रिप्टर में निर्देशित करता है जो इसके मूल (पायथन) प्रक्रिया को वापस फीड करता है। पैरेंट की ओर, आउटपुट को पढ़ने के लिए केवल एक ही पाइप-डिस्क्रिप्टर है। सभी "stderr" आउटपुट में दिखाई देता है proc.stdout, और यदि आप कॉल करते हैं proc.communicate(), तो stderr परिणाम (टुपल में दूसरा मूल्य) होगा None, न कि एक स्ट्रिंग।

कठिन मामले: दो या अधिक पाइप

जब आप कम से कम दो पाइप का उपयोग करना चाहते हैं तो सभी समस्याएं आती हैं। वास्तव में, subprocessकोड में ही यह बिट है:

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:

लेकिन, अफसोस, यहाँ हमने कम से कम दो, और शायद तीन, अलग-अलग पाइप बनाए हैं, इसलिए count(None)रिटर्न या तो 1 या 0. हमें चीजों को कठिन तरीके से करना चाहिए।

विंडोज पर, यह threading.Threadपरिणाम के लिए self.stdoutऔर जमा करने के लिए उपयोग करता है self.stderr, और इसमें पैरेंट थ्रेड डिलीवर self.stdinइनपुट डेटा होता है (और फिर पाइप को बंद करें)।

POSIX पर, यह pollउपलब्ध का उपयोग करता है, अन्यथा select, आउटपुट को संचित करने और स्टडिन इनपुट देने के लिए। यह सब (एकल) मूल प्रक्रिया / धागे में चलता है।

गतिरोध से बचने के लिए थ्रेड्स या पोल / सेलेक्ट की आवश्यकता है। उदाहरण के लिए, मान लीजिए कि हमने सभी तीन धाराओं को तीन अलग-अलग पाइपों पर पुनर्निर्देशित किया है। मान लीजिए कि लेखन प्रक्रिया निलंबित होने से पहले पाइप में कितना डेटा भरा जा सकता है, इसकी एक छोटी सी सीमा है, दूसरे छोर से पाइप को "साफ" करने के लिए पढ़ने की प्रक्रिया की प्रतीक्षा कर रहा है। आइए चित्रण के लिए उस छोटी सी सीमा को एक बाइट पर सेट करें। (यह वास्तव में है कि चीजें कैसे काम करती हैं, सिवाय इसके कि सीमा एक बाइट से बहुत बड़ी है।)

माता-पिता (अजगर) प्रक्रिया की कोशिश करता कई बाइट्स-कहते हैं, लिखने के लिए तो 'go\n'करने के लिए proc.stdin, पहली बाइट में चला जाता है और फिर दूसरी अजगर प्रक्रिया का कारण बनता है को निलंबित करने, उपप्रक्रिया के लिए इंतज़ार कर पहले बाइट को पढ़ने के लिए, पाइप खाली।

इस बीच, मान लीजिए कि उपप्रकार एक दोस्ताना "हैलो! डोंट पैनिक" प्रिंट करने का फैसला करता है। शुभकामना। Hइसके stdout पाइप में चला जाता है, लेकिन eनिलंबित करने के लिए यह कारण बनता है, इसके जनक के लिए है कि पढ़ने के लिए इंतजार करH , stdout पाइप खाली।

अब हम फंस गए हैं: पायथन प्रक्रिया सो रही है, "जाओ" कहकर समाप्त करने की प्रतीक्षा कर रहा है, और उपप्रकार भी सो रहा है, "हैलो! डोंट पैनिक!" कह कर समाप्त होने की प्रतीक्षा कर रहा है।

subprocess.Popenकोड सूत्रण या चयन / चुनाव के साथ इस समस्या से बचा जाता है। जब बाइट्स पाइप के ऊपर जा सकते हैं, तो वे जाते हैं। जब वे नहीं कर सकते हैं, तो केवल एक थ्रेड (पूरी प्रक्रिया नहीं) को सोना है - या, चयन / सर्वेक्षण के मामले में, पायथन प्रक्रिया एक साथ "प्रतीक्षा" या "डेटा उपलब्ध" के लिए इंतजार कर सकती है, प्रक्रिया की गति को लिखती है। केवल जब वहाँ कमरा है, और इसके स्टडआउट और / या stderr को केवल तभी पढ़ता है जब डेटा तैयार होता है। proc.communicate()कोड (वास्तव में _communicate, जहां बालों मामलों नियंत्रित किया जाता है) रिटर्न सब stdin डेटा एक बार (यदि हो तो) भेज दिया गया है और सभी stdout और / या stderr डेटा जमा किया गया है।

यदि आप दोनों stdoutऔर stderrदो अलग-अलग पाइपों (किसी भी stdinपुनर्निर्देशन की परवाह किए बिना ) को पढ़ना चाहते हैं , तो आपको गतिरोध से भी बचने की आवश्यकता होगी। यहां डेडलॉक परिदृश्य अलग है- यह तब होता है जब उपप्रोसेस कुछ लंबा लिखता है stderrजब आप डेटा खींच रहे होते हैं stdout, या इसके विपरीत - लेकिन यह अभी भी है।


डेमो

मैंने यह प्रदर्शित करने का वादा किया है कि, संयुक्त राष्ट्र पुनर्निर्देशित, पायथन subprocessएश अंतर्निहित स्टडआउट को लिखता है, नहीं sys.stdout। तो, यहाँ कुछ कोड है:

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

जब चला:

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

start show2
hello

ध्यान दें कि पहले दिनचर्या यदि आप जोड़ने के असफल हो जायेगी stdout=sys.stdout, के रूप में एक StringIOवस्तु नहीं है fileno। दूसरा छोड़ जाएगा helloअगर आप को जोड़ने stdout=sys.stdoutके बाद से sys.stdoutपर पुनः निर्देशित कर दिया गया है os.devnull

(यदि आप पायथन के फ़ाइल-डिस्क्रिप्टर -1 को पुनर्निर्देशित करते हैं , तो उपप्रकार उस पुनर्निर्देशन का पालन करेगाopen(os.devnull, 'w')कॉल एक स्ट्रीम उत्पन्न करता है, जो fileno()2. से अधिक है।)


हम्म। आपका डेमो अंत में दावे के विपरीत दिखाने लगता है। आप पाइथन के स्टडआउट को बफर में पुनः निर्देशित कर रहे हैं, लेकिन सबप्रोसेस स्टडआउट अभी भी कंसोल पर जा रहा है। यह कैसे उपयोगी है? क्या मैं कुछ भूल रहा हूँ?
गाय सिरटन

@GuySirton: डेमो से पता चलता है कि सबप्रोसेस स्टैडआउट (जब स्पष्ट रूप से निर्देशित नहीं किया sys.stdoutजाता है) पायथन के स्टडआउट में जाता है , न कि पाइथन प्रोग्राम के ( sys.) स्टडआउट से। जो मैं मानता हूँ ... एक अजीब अंतर है। क्या यह वाक्यांश का एक बेहतर तरीका है?
torek

यह जानना अच्छा है, लेकिन हम वास्तव में यहाँ सबप्रोसेस आउटपुट को कैप्चर करना चाहते हैं ताकि बदलते sys.stdout शांत हों, लेकिन मुझे लगता है कि हमारी मदद नहीं करता है। अच्छा अवलोकन जो संवाद का चयन (), पोल या थ्रेड जैसे कुछ का उपयोग कर रहा होगा।
गाय सिरटन


मैंने चयन के साथ एक कार्यान्वयन जोड़ा है ()
शिवानी

20

हम रीडलाइन () के साथ पुनरावृति निर्माण का उपयोग करने के बजाय stdout पढ़ने के लिए डिफ़ॉल्ट फ़ाइल पुनरावृत्ति का भी उपयोग कर सकते हैं।

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

सबसे सुरुचिपूर्ण जवाब यहाँ!
Nir

9
यह समाधान वास्तविक समय में प्रदर्शित नहीं होता है। यह प्रक्रिया पूरी होने तक प्रतीक्षा करता है और एक ही बार में सभी आउटपुट प्रदर्शित करता है। विक्टर केर्केज़ के समाधान में, यदि "your_command" उत्तरोत्तर प्रदर्शित होता है, तो उत्पादन उत्तरोत्तर चलता है, जब तक कि "your_command" समय-समय पर (पाइप के कारण) फ्लशआउट हो जाता है।
एरिक एच।

1
@ क्योंकि यह जीना नहीं है।
मेलमास

यह समाधान डिफ़ॉल्ट डिस्क्रिप्टर पर प्रसारित होता है, इसलिए यह केवल तभी अपडेट होगा जब आउटपुट में एक लाइन अपडेट होती है। एक चरित्र आधारित अद्यतन के लिए आपको रीड () पद्धति पर पुनरावृति करने की आवश्यकता है जैसा कि विक्टर के समाधान में दिखाया गया है। लेकिन यह मेरे उपयोग के मामले के लिए एक overkill था।
जुगहेड

12

यदि आप तृतीय-पक्ष लाइब्रेरी का उपयोग करने में सक्षम हैं, तो आप कुछ का उपयोग करने में सक्षम हो सकते हैं sarge(प्रकटीकरण: मैं इसका अनुचर हूं)। यह लाइब्रेरी उपप्रोसेस से आउटपुट स्ट्रीम तक नॉन-ब्लॉकिंग एक्सेस की अनुमति देता है - यह subprocessमॉड्यूल पर स्तरित है ।


सर्ज, बीटीडब्ल्यू पर ठीक काम। यह वास्तव में ओपी की आवश्यकता को हल करता है, लेकिन उस उपयोग के मामले के लिए थोड़ा भारी हो सकता है।
गहरी

4

समाधान 1: लॉग stdoutऔर stderrसमवर्ती realtime में

एक सरल समाधान जो दोनों stdout और stderr को समवर्ती रूप से लॉग-इन करता है, लाइन-बाय-लाइन को एक लॉग फ़ाइल में रियलटाइम में करता है।

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

समाधान 2: एक ऐसा कार्य read_popen_pipes()जो आपको वास्तविक समय में समवर्ती रूप से दोनों पाइप (stdout / stderr) पर पुनरावृति करने की अनुमति देता है

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

एक अच्छा लेकिन "हैवीवेट" समाधान ट्विस्टेड का उपयोग करना है - नीचे देखें।

यदि आप केवल उन चीज़ों के साथ काम करने के लिए तैयार रहना चाहते हैं, जिन्हें काम करना चाहिए:

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

(यदि आप रीड का उपयोग करते हैं () यह पूरी "फाइल" को पढ़ने की कोशिश करता है, जो उपयोगी नहीं है, तो हम वास्तव में यहां जो उपयोग कर सकते हैं वह कुछ ऐसा है जो सभी डेटा को पढ़ता है जो अभी पाइप में है)

कोई थ्रेडिंग के साथ भी संपर्क करने की कोशिश कर सकता है, जैसे:

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

अब हम संभावित रूप से दो थ्रेड होने के साथ-साथ स्टेडर भी जोड़ सकते हैं।

हालाँकि नोट करें कि उपप्रोसेस डॉक्स इन फ़ाइलों को सीधे उपयोग करने के लिए हतोत्साहित करते हैं और उपयोग करने की सलाह देते हैं communicate()(ज्यादातर डेडलॉक से संबंधित है जो मुझे लगता है कि ऊपर कोई समस्या नहीं है) और समाधान थोड़े क्लंकी हैं इसलिए ऐसा लगता है कि वास्तव में सबप्रोसेस मॉड्यूल काफी ऊपर है। काम (यह भी देखें: http://www.python.org/dev/peps/pep-3145/ ) और हमें कुछ और देखने की जरूरत है।

अधिक शामिल समाधान का उपयोग करना है मुड़ जैसा कि यहाँ दिखाया: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html

जिस तरह से आप इसे ट्विस्टेडreactor.spawnprocess() करते हैं, ProcessProtocolउसी तरह आउटपुट को असिंक्रोनस तरीके से प्रोसेस करने और प्रदान करने के लिए अपनी प्रक्रिया बनाना है । ट्विस्टेड नमूना पायथन कोड यहाँ है: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py


धन्यवाद! मैंने बस कुछ इस तरह की कोशिश की (@PauloAlmeida टिप्पणी पर आधारित है, लेकिन उपप्रोसेस करने के लिए मेरा फोन बंद कर रहा है। - यह केवल एक बार लौटते समय लूप पर आता है ...
Dil लिथियमMatrix

1
वही नहीं चल रहा है। यह ठीक उसी समय लूप में प्रवेश कर रहा है, जब read()तक कि सबप्रोसेस बाहर नहीं निकल जाता और EOFपाइप पर पैरेंट प्रक्रिया प्राप्त नहीं हो जाती, तब तक कॉल को ब्लॉक किया जाता है ।
Alp

@ दिलचस्प दिलचस्प! इसलिए यह।
Dil लिथियमMatrix

हाँ, मुझे यह पोस्ट करने की बहुत जल्दी थी। यह वास्तव में ठीक से काम नहीं करता है और आसानी से तय नहीं किया जा सकता है। वापस ड्राइंग टेबल पर।
गाय सिरटन

1
@zhermes: तो रीड () के साथ समस्या यह है कि यह EOF तक पूरे आउटपुट को पढ़ने की कोशिश करेगा जो उपयोगी नहीं है। रीडलाइन () मदद करता है और आप सभी की जरूरत हो सकती है (वास्तव में लंबी लाइनें भी एक समस्या हो सकती हैं)। आपको उस प्रक्रिया में बफरिंग के लिए भी देखना होगा जो आप शुरू कर रहे हैं ...
गाय सिरटन

3

इन सभी जवाबों के अलावा, एक सरल तरीका इस प्रकार भी हो सकता है:

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

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

    if not line:
        break

    print(line.strip())

जब तक यह पठनीय है, तब तक पठनीय धारा के माध्यम से लूप करें और यदि यह एक खाली परिणाम प्राप्त करता है, तो इसे रोक दें।

यहां कुंजी यह है कि readline()एक लाइन (साथ) लौटाता है\n जब तक यह वास्तव में अंत में है, तब तक एक आउटपुट और खाली रहता है (यदि अंत में) ।

आशा है कि यह किसी की मदद करता है।


3

उपरोक्त सभी के आधार पर मैं थोड़ा संशोधित संस्करण (python3) सुझाता हूं:

  • लूप कॉलिंग रीडलाइन के दौरान (इट्रर सॉल्यूशन ने सुझाव दिया था कि मुझे हमेशा के लिए ब्लॉक कर दिया जाए - पायथन 3, विंडोज 7)
  • पढ़े गए डेटा से निपटने के लिए संरचित की जरूरत नहीं है, क्योंकि चुनाव रिटर्न के बाद डुप्लिकेट करने की आवश्यकता नहीं है-None
  • stderr को stdout में पाइप किया जाता है ताकि दोनों आउटपुट आउटपुट पढ़े जा सकें
  • Cmd का निकास मान प्राप्त करने के लिए कोड जोड़ा गया।

कोड:

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

returncodeहिस्सा मेरे मामले में महत्वपूर्ण था।
स्टारडस्ट

2

ऐसा लगता है कि लाइन-बफ़र्ड आउटपुट आपके लिए काम करेगा, जिस स्थिति में निम्नलिखित जैसा कुछ हो सकता है। (कैविएट: यह अप्रयुक्त है।) यह केवल वास्तविक समय में सबप्रोसेस की स्टडआउट देगा। यदि आप वास्तविक समय में stderr और stdout दोनों चाहते हैं, तो आपको कुछ और जटिल करना होगा 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

stdoutसीधे सेट क्यों नहीं sys.stdout? और अगर आपको लॉग के रूप में अच्छी तरह से आउटपुट करने की आवश्यकता है, तो आप बस एफ के लेखन विधि को ओवरराइड कर सकते हैं।

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)

यह काम नहीं करेगा: उपप्रोसेस मॉड्यूल कांटे और stdoutफ़ाइल डिस्क्रिप्टर को फाइल की गई वस्तु के फाइल डिस्क्रिप्टर पर सेट करता है । राइट-मेथड कभी नहीं कहा जाएगा (कम से कम यह है कि उपप्रकार stderr के लिए करता है, मुझे लगता है कि यह stoutout के लिए समान है)।
t.animal

2

उपरोक्त सभी समाधान मैंने कोशिश की या तो stderr और stdout आउटपुट को अलग करने में विफल रहा, (कई पाइप) या हमेशा के लिए अवरुद्ध जब OS पाइप बफर भरा हुआ था, जो तब होता है जब आप जिस आउटपुट को बहुत तेजी से चालू कर रहे हैं कमांड (इसके लिए एक चेतावनी है अजगर पर पोल () उपप्रकार का मैनुअल)। एकमात्र विश्वसनीय तरीका जो मुझे मिला, वह चयन के माध्यम से था, लेकिन यह केवल एक सकारात्मक समाधान है:

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

एक अन्य विकल्प एक धागा प्रति पाइप से स्पिन करना है। प्रत्येक धागा पाइप पर I / O को अवरुद्ध कर सकता है, दूसरे धागे को अवरुद्ध किए बिना। लेकिन यह मुद्दों के अपने सेट का परिचय देता है। सभी विधियों में झुंझलाहट है, आप बस वह चुन लेते हैं जिसे आप कम से कम कष्टप्रद पाते हैं। :-)
torek

2

पिछले उत्तरों के समान लेकिन निम्नलिखित समाधान ने मेरे लिए विंडोज़ पर काम किया है जो Python3 का उपयोग करके प्रिंट करने और रियलटाइम में लॉग इन करने के लिए एक सामान्य विधि प्रदान करता है ( प्राप्त-रियलटाइम-आउटपुट- यूज़िंग -पायथन ):

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

मुझे लगता है कि subprocess.communicateविधि थोड़ा भ्रामक है: यह वास्तव में भर जाता है stdout और stderr है कि आप में निर्दिष्ट subprocess.Popen

फिर भी, से पढ़ subprocess.PIPEआप के लिए प्रदान कर सकते हैं कि subprocess.Popenके लिए stdout और stderr मापदंडों अंततः ओएस पाइप बफ़र्स को भरने और अपने अनुप्रयोग गतिरोध (खासकर यदि आप कई प्रक्रियाओं / धागे कि है जाएगा चाहिए उपयोगsubprocess )।

मेरा प्रस्तावित समाधान फाइलों के साथ स्टडआउट और स्टेडर प्रदान करना है - और गतिरोध से पढ़ने के बजाय फाइलों की सामग्री को पढ़ना PIPE। ये फाइलें हो सकती हैं tempfile.NamedTemporaryFile()- जिन्हें पढ़ने के लिए भी एक्सेस किया जा सकता है जबकि वे इसमें लिखी जा रही हैं subprocess.communicate

नीचे एक नमूना उपयोग है:

        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

और यह स्रोत कोड है जो उपयोग करने के लिए तैयार है कि कई टिप्पणियों के साथ जैसा कि मैं यह बताने के लिए प्रदान कर सकता हूं कि यह क्या करता है:

यदि आप अजगर 2 का उपयोग कर रहे हैं, तो कृपया सबसे पहले pypi से subprocess32 पैकेज का नवीनतम संस्करण स्थापित करना सुनिश्चित करें ।


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

यहाँ एक वर्ग है जो मैं अपने एक प्रोजेक्ट में उपयोग कर रहा हूँ। यह लॉग में एक उपप्रकार के आउटपुट को रीडायरेक्ट करता है। पहले तो मैंने केवल लिखने की विधि को अधिलेखित करने की कोशिश की, लेकिन यह काम नहीं करता क्योंकि उपप्रकार इसे कभी भी कॉल नहीं करेगा (पुनर्निर्देशन फ़ाइल नामांकित व्यक्ति के स्तर पर होता है)। इसलिए मैं अपने स्वयं के पाइप का उपयोग कर रहा हूं, यह सबप्रोसेस-मॉड्यूल में कैसे किया जाता है, इसके समान है। इससे एडॉप्टर में सभी लॉगिंग / प्रिंटिंग लॉजिक को इनकैप्सुलेट करने का फायदा होता है और आप बस लकड़हारे के उदाहरणों को पास कर सकते हैं 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())

यदि आपको लॉगिंग की आवश्यकता नहीं है, लेकिन बस print()आप उपयोग करना चाहते हैं तो आप स्पष्ट रूप से कोड के बड़े हिस्से को हटा सकते हैं और कक्षा को छोटा रख सकते हैं। तुम भी एक से यह विस्तृत कर सकते हैं __enter__और __exit__विधि और फोन finishedमें __exit__है कि आप आसानी से संदर्भ के रूप में यह इस्तेमाल कर सकते हैं तो।


1

पाइथोनिक समाधानों में से किसी ने भी मेरे लिए काम नहीं किया। यह पता चला कि proc.stdout.read()या समान हमेशा के लिए अवरुद्ध हो सकता है।

इसलिए, मैं teeइस तरह का उपयोग करें:

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

यदि आप पहले से ही उपयोग कर रहे हैं तो यह समाधान सुविधाजनक है shell=True

${PIPESTATUS}पूरी कमांड श्रृंखला की सफलता की स्थिति पर कब्जा (केवल बाश में उपलब्ध)। अगर मैं छोड़ दिया && exit ${PIPESTATUS}, तो यह हमेशा के बाद से शून्य वापस आ जाएगीtee कभी भी विफल नहीं होता है।

unbufferटर्मिनल में तुरंत प्रत्येक पंक्ति को प्रिंट करने के लिए आवश्यक हो सकता है, जब तक कि "पाइप बफर" भर जाने तक प्रतीक्षा करने के बजाय बहुत लंबा रास्ता तय किया जाए। हालांकि, अनबाउंडर मुखर (एसआईजी एबॉर्ट) की निकास स्थिति को निगल लेता है ...

2>&1 फ़ाइल के लिए stderror भी लॉग करता है।

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