2013-08-20 11 views
23

मेरे पास एक कमांड लाइन उपकरण (वास्तव में, कई) है कि मैं पाइथन में एक रैपर लिख रहा हूं।पायथन: subprocess.call, फ़ाइल करने के लिए stdout, फ़ाइल के लिए stderr, वास्तविक समय में स्क्रीन पर stderr प्रदर्शित करें

उपकरण आम तौर पर इस तरह प्रयोग किया जाता है:

$ path_to_tool -option1 -option2 > file_out 

उपयोगकर्ता उत्पादन file_out के लिए लिखा जाता है, और भी उपकरण के विभिन्न स्थिति संदेश को देखने के लिए के रूप में यह चल रहा है में सक्षम है।

मैं इस व्यवहार को दोहराना चाहता हूं, जबकि फ़ाइल में stderr (स्थिति संदेश) को लॉगिंग करना भी चाहता हूं।

यह क्या मेरे पास है:

from subprocess import call 
call(['path_to_tool','-option1','option2'], stdout = file_out, stderr = log_file) 

यह है कि stderr छोड़कर ठीक काम करता है स्क्रीन के लिए लिखा नहीं है। मैं पाठ्यक्रम की स्क्रीन पर log_file की सामग्री को मुद्रित करने के लिए कोड जोड़ सकता हूं, लेकिन फिर यह घटित होने के बजाए सब कुछ करने के बाद उपयोगकर्ता इसे देखेगा।

संक्षेप में

, वांछित व्यवहार है:

  1. उपयोग कॉल(), या उपप्रक्रिया()
  2. एक फ़ाइल
  3. एक फाइल करने के लिए प्रत्यक्ष stderr के लिए सीधी stdout, जबकि यह भी स्क्रीन पर stderr लेखन वास्तविक समय में जैसे उपकरण को कमांड लाइन से सीधे कॉल किया गया था।

मुझे एहसास है कि मैं या तो कुछ सचमुच सरल याद कर रहा हूं, या यह सोचने से कहीं अधिक जटिल है ... किसी भी मदद के लिए धन्यवाद!

संपादित करें: इसे केवल लिनक्स पर काम करने की आवश्यकता है।

+0

अपने कोड विंडोज (या अन्य गैर POSIXy प्लेटफार्मों) पर काम करने की जरूरत है? यदि नहीं, तो एक आसान जवाब है। – abarnert

+0

इसकी आवश्यकता नहीं है! –

+0

संबंधित: [पायथन उपप्रोसेज को बच्चों के आउटपुट को फ़ाइल और टर्मिनल में मिलता है?] (Http://stackoverflow.com/q/4984428/4279) – jfs

उत्तर

52

आप subprocess के साथ ऐसा कर सकते हैं, लेकिन यह मामूली नहीं है। यदि आप दस्तावेज़ों में Frequently Used Arguments देखते हैं, तो आप देखेंगे कि आप PIPE को stderr तर्क के रूप में पास कर सकते हैं, जो एक नई पाइप बनाता है, पाइप के एक तरफ बच्चे की प्रक्रिया में गुजरता है, और दूसरी तरफ उपयोग करने के लिए उपलब्ध कराता है stderr विशेषता। *

तो, आपको उस पाइप की सेवा करने की आवश्यकता होगी, स्क्रीन पर और फ़ाइल में लिखना होगा। आम तौर पर, इसके लिए विवरण प्राप्त करना बहुत मुश्किल है। ** आपके मामले में, केवल एक पाइप है, और आप इसे समकालिक रूप से सर्विस करने की योजना बना रहे हैं, इसलिए यह बुरा नहीं है।

import subprocess 
proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'], 
         stdout=file_out, stderr=subprocess.PIPE) 
for line in proc.stderr: 
    sys.stdout.write(line) 
    log_file.write(line) 
proc.wait() 

(ध्यान दें कि कुछ for line in proc.stderr: उपयोग करने में समस्याएं -basically हैं कि, क्या आप पढ़ रहे हैं पता चला है कि अगर किसी भी कारण से लाइन बफ़र नहीं होने के लिए आपको एक नई पंक्ति के लिए इंतज़ार कर चारों ओर बैठ सकते हैं वहाँ वास्तव में आधा है, भले ही प्रक्रिया के लिए डेटा की एक लाइन मूल्य। यदि आवश्यक हो तो डेटा को अधिक सुचारु रूप से प्राप्त करने के लिए, read(128), या यहां तक ​​कि read(1) के साथ, आप एक बार में भाग पढ़ सकते हैं। यदि आपको वास्तव में आने वाले हर बाइट को वास्तव में प्राप्त करने की आवश्यकता होती है, और कर सकते हैं read(1) की लागत का खर्च नहीं उठाएगा, आपको पाइप को गैर-अवरुद्ध मोड में डालना होगा और अतुल्यकालिक रूप से पढ़ना होगा।)


लेकिन यदि आप यूनिक्स पर हैं, तो tee कमांड का उपयोग करने के लिए यह आसान हो सकता है।

त्वरित & गंदे समाधान के लिए, आप खोल के माध्यम से पाइप का उपयोग कर सकते हैं। इस तरह कुछ:

subprocess.call('path_to_tool -option1 option2 2|tee log_file 1>2', shell=True, 
       stdout=file_out) 

लेकिन मैं खोल पाइपिंग डीबग नहीं करना चाहता; यह अजगर में करते हैं, जैसा कि दिखाया गया in the docs करते हैं:

tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'], 
         stdout=file_out, stderr=subprocess.PIPE) 
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stderr) 
tool.stderr.close() 
tee.communicate() 

अंत में, एक दर्जन या अधिक subprocesses और/या PyPI- sh, shell, shell_command, shellout पर खोल के आसपास उच्च स्तर के रैपर, iterpipes, sarge, cmd_utils, commandwrapper, आदि "खोल", "सबप्रोसेस", "प्रक्रिया", "कमांड लाइन" आदि के लिए खोजें और अपनी पसंद के किसी को ढूंढें जो समस्या को तुच्छ बनाता है।


क्या होगा यदि आपको दोनों stderr और stdout को इकट्ठा करने की आवश्यकता है?

ऐसा करने का आसान तरीका सिर्फ एक को दूसरे पर रीडायरेक्ट करना है, क्योंकि स्वेन मार्नैच ने एक टिप्पणी में सुझाव दिया है। बस इस तरह Popen पैरामीटर बदल:।

tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'], 
         stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 

और फिर हर जगह आप tool.stderr इस्तेमाल किया, tool.stdout बजाय-उदाहरण के लिए: उपयोग करते हैं, पिछले उदाहरण के लिए:

tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stdout) 
tool.stdout.close() 
tee.communicate() 

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


यदि आपको दो प्रक्रियाओं को अलग से संभालने की आवश्यकता है, तो यह अधिक कठिन हो जाता है। इससे पहले, मैंने कहा था कि फ्लाई पर पाइप की सेवा करना तब तक आसान है जब तक आपके पास केवल एक पाइप हो और इसे समकालिक रूप से सेवा दे सके। यदि आपके पास दो पाइप हैं, तो यह स्पष्ट रूप से अब सत्य नहीं है। कल्पना करें कि आप tool.stdout.read() पर प्रतीक्षा कर रहे हैं, और नया डेटा tool.stderr से आता है। यदि बहुत अधिक डेटा है, तो यह पाइप को ओवरफ्लो और उपप्रोसेस को अवरुद्ध कर सकता है। लेकिन अगर ऐसा नहीं होता है, तो आप स्पष्ट रूप से stdr डेटा को पढ़ने और लॉग इन करने में सक्षम नहीं होंगे जब तक कि stdout से कुछ नहीं आता है।

यदि आप पाइप-थ्रू -tee समाधान का उपयोग करते हैं, जो प्रारंभिक समस्या से बचाता है ... लेकिन केवल एक नई परियोजना बनाकर जो खराब है। आपके पास दो tee उदाहरण हैं, और जब आप एक पर communicate पर कॉल कर रहे हैं, तो दूसरा हमेशा के लिए प्रतीक्षा कर रहा है।

तो, किसी भी तरह से, आपको किसी प्रकार की असीमित तंत्र की आवश्यकता है। आप यह थ्रेड के साथ कर सकते हैं, select रिएक्टर, gevent आदि जैसे कुछ।

proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'], 
         stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
def tee_pipe(pipe, f1, f2): 
    for line in pipe: 
     f1.write(line) 
     f2.write(line) 
t1 = threading.Thread(target=tee_pipe, args=(proc.stdout, file_out, sys.stdout)) 
t2 = threading.Thread(target=tee_pipe, args=(proc.stderr, log_file, sys.stderr)) 
t3 = threading.Thread(proc.wait) 
t1.start(); t2.start(); t3.start() 
t1.join(); t2.join(); t3.join() 

हालांकि, कुछ मामलों में जहां बढ़त है कि काम नहीं करेगा कर रहे हैं:

यहां एक त्वरित और गंदे उदाहरण है। (समस्या वह क्रम है जिसमें सिग्चाल्ड और सिगिपिप/ईपीआईपीई/ईओएफ आते हैं। मुझे नहीं लगता कि इनमें से कोई भी हमें प्रभावित करेगा, क्योंकि हम कोई इनपुट नहीं भेज रहे हैं ... लेकिन मुझे इस पर भरोसा न करें के माध्यम से और/या परीक्षण।) 3.3+ से subprocess.communicate फ़ंक्शन सभी स्पष्ट विवरण सही हो जाता है। लेकिन आपको एपीआईएनसी-सबप्रोसेस रैपर कार्यान्वयन में से एक का उपयोग करना बहुत आसान हो सकता है जो आप पीपीपीआई और एक्टिवस्टेट पर पा सकते हैं, या यहां तक ​​कि उप-प्रोसेस सामग्री जैसे ट्विस्टेड जैसे पूर्ण एसिंक फ्रेमवर्क से भी मिल सकते हैं।


* डॉक्स वास्तव में व्याख्या नहीं करते क्या पाइप कर रहे हैं, लगभग रूप में यदि वे आप एक पुराने यूनिक्स सी हाथ होने की उम्मीद ... लेकिन कुछ उदाहरण, विशेष रूप से Replacing Older Functions with the subprocess Module अनुभाग में, दिखाने के वे कैसे कर रहे हैं इस्तेमाल किया, और यह बहुत आसान है।

** कठिन हिस्सा दो या दो से अधिक पाइप ठीक से अनुक्रमित कर रहा है। यदि आप एक पाइप पर इंतजार करते हैं, तो दूसरा ओवरफ्लो और अवरुद्ध हो सकता है, जिससे आप किसी अन्य को कभी खत्म होने से रोक सकते हैं। इसके चारों ओर जाने का एकमात्र आसान तरीका प्रत्येक पाइप की सेवा के लिए धागा बनाना है। (अधिकांश * निक्स प्लेटफार्मों पर, आप select या poll रिएक्टर का उपयोग कर सकते हैं, लेकिन यह क्रॉस-प्लेटफ़ॉर्म आश्चर्यजनक रूप से कठिन है।) The source मॉड्यूल में, विशेष रूप से communicate और इसके सहायक, यह दिखाता है कि यह कैसे करें। (मैं 3.3 से जुड़ा हुआ हूं, क्योंकि पिछले संस्करणों में, communicate स्वयं कुछ महत्वपूर्ण चीजें गलत हो जाता है ...) यही कारण है कि, जब भी संभव हो, आप communicate का उपयोग करना चाहते हैं यदि आपको एक से अधिक पाइप की आवश्यकता है। आपके मामले में, आप communicate का उपयोग नहीं कर सकते हैं, लेकिन सौभाग्य से आपको एक से अधिक पाइप की आवश्यकता नहीं है।

+1

बहुत उपयोगी, धन्यवाद। क्या आप पी 1 और पी 2 लिखना चाहते थे? –

+0

@ user2063292: क्षमा करें, यह 'टूल' और' टीई' है। नमूना कोड के बाद थोड़ा बहुत बारीकी से। :) इसे पकड़ने के लिए धन्यवाद। – abarnert

+0

'2 | 'पाइप stderr होना चाहिए? यह POSIX खोल में नहीं है। –

0

मुझे लगता है कि आप देख रहे हैं कुछ की तरह है:

import sys, subprocess 
p = subprocess.Popen(cmdline, 
        stdout=sys.stdout, 
        stderr=sys.stderr) 

, उत्पादन/एक फाइल करने के लिए लिखा लॉग मैं अपने cmdline संशोधित सामान्य रीडायरेक्ट शामिल करने के लिए होता है करने के लिए के रूप में यह एक सादे पर किया जाना होगा लिनक्स बैश/खोल। उदाहरण के लिए, मैं tee को कमांड लाइन में जोड़ूंगा: cmdline += ' | tee -a logfile.txt'

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

0

मैं यह काम करने लगता है पायथन 3. के लिए कुछ @ abarnert के जवाब में परिवर्तन करने के लिए किया था:

def tee_pipe(pipe, f1, f2): 
    for line in pipe: 
     f1.write(line) 
     f2.write(line) 

proc = subprocess.Popen(["/bin/echo", "hello"], 
         stdout=subprocess.PIPE, 
         stderr=subprocess.PIPE) 

# Open the output files for stdout/err in unbuffered mode. 
out_file = open("stderr.log", "wb", 0) 
err_file = open("stdout.log", "wb", 0) 

stdout = sys.stdout 
stderr = sys.stderr 

# On Python3 these are wrapped with BufferedTextIO objects that we don't 
# want. 
if sys.version_info[0] >= 3: 
    stdout = stdout.buffer 
    stderr = stderr.buffer 

# Start threads to duplicate the pipes. 
out_thread = threading.Thread(target=tee_pipe, 
           args=(proc.stdout, out_file, stdout)) 
err_thread = threading.Thread(target=tee_pipe, 
           args=(proc.stderr, err_file, stderr)) 

out_thread.start() 
err_thread.start() 

# Wait for the command to finish. 
proc.wait() 

# Join the pipe threads. 
out_thread.join() 
err_thread.join() 
संबंधित मुद्दे