सबप्रोसेस से रियलटाइम में स्टडआउट पकड़ना


87

मैं subprocess.Popen()विंडोज में rsync.exe करना चाहता हूं , और पायथन में स्टडआउट प्रिंट करता हूं ।

मेरा कोड काम करता है, लेकिन यह तब तक प्रगति को नहीं पकड़ता है जब तक कि फाइल ट्रांसफर न हो जाए! मैं वास्तविक समय में प्रत्येक फ़ाइल के लिए प्रगति प्रिंट करना चाहता हूं।

पायथन 3.1 का उपयोग करते हुए अब मैंने सुना है कि आईओ को संभालने में बेहतर होना चाहिए।

import subprocess, time, os, sys

cmd = "rsync.exe -vaz -P source/ dest/"
p, line = True, 'start'


p = subprocess.Popen(cmd,
                     shell=True,
                     bufsize=64,
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)

for line in p.stdout:
    print(">>> " + str(line.rstrip()))
    p.stdout.flush()


1
(Google से आ रहा है?) सभी PIPE तब गतिरोध करेंगे जब PIPEs के बफर में से एक भर जाता है और पढ़ा नहीं जाता है। जैसे stderout भरा हुआ है जब stdout गतिरोध। कभी नहीं एक पीआईपीई पास करें जिसे आपने पढ़ने का इरादा नहीं किया है।
नासर अल-वोहबी

क्या कोई समझा सकता है कि आप सबप्रोसेस की बजाय sys.stdout को stdout क्यों सेट नहीं कर सकते। PIPE?
माइक

जवाबों:


96

के लिए अंगूठे के कुछ नियम subprocess

  • कभी उपयोग करें shell=True। यह अनावश्यक रूप से आपके प्रोग्राम को कॉल करने के लिए एक अतिरिक्त शेल प्रक्रिया को आमंत्रित करता है।
  • कॉल करने की प्रक्रिया के दौरान, तर्कों को सूचियों के रूप में पास किया जाता है। sys.argvअजगर में एक सूची है, और इसलिए argvसी में है। तो आप एक स्ट्रिंग नहीं उप-वर्गों को कॉल करने के लिए एक सूची पास करते हैं Popen
  • रीडायरेक्ट न करें stderrएक करने के लिए PIPEजब आप इसे पढ़ नहीं रहे हैं।
  • stdinजब आप इसे नहीं लिख रहे हैं तो पुनर्निर्देशित न करें ।

उदाहरण:

import subprocess, time, os, sys
cmd = ["rsync.exe", "-vaz", "-P", "source/" ,"dest/"]

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

for line in iter(p.stdout.readline, b''):
    print(">>> " + line.rstrip())

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

इसके लिए परीक्षण करने के लिए, इसके बजाय इसे चलाने का प्रयास करें:

cmd = [sys.executable, 'test_out.py']

और test_out.pyसामग्री के साथ एक फ़ाइल बनाएँ :

import sys
import time
print ("Hello")
sys.stdout.flush()
time.sleep(10)
print ("World")

यह कहते हुए कि उपप्रकार आपको "हैलो" देना चाहिए और "दुनिया" देने से पहले 10 सेकंड प्रतीक्षा करें। यदि ऐसा ऊपर के अजगर कोड के साथ होता है और इसके साथ नहीं होता है rsync, तो इसका मतलब rsyncहै कि इसका आउटपुट बफ़र कर रहा है, इसलिए आप भाग्य से बाहर हैं।

एक समाधान के लिए प्रत्यक्ष कनेक्ट करने के लिए होगा pty, जैसे कुछ का उपयोग कर pexpect


12
shell=Falseजब आप विशेष रूप से उपयोगकर्ता द्वारा दर्ज की गई कमांड लाइन का निर्माण करते हैं, तो यह सही बात है। लेकिन shell=Trueतब भी उपयोगी है जब आपको विश्वसनीय स्रोत से पूरी कमांड लाइन मिलती है (उदाहरण के लिए स्क्रिप्ट में हार्डकोड)।
डेनिस ओटकिडैच

10
@ डेनिस ओटकिडैच: मुझे नहीं लगता कि वारंट का उपयोग shell=True। इसके बारे में सोचो - आप अपने ओएस पर एक और प्रक्रिया लागू कर रहे हैं, जिसमें मेमोरी आवंटन, डिस्क उपयोग, प्रोसेसर शेड्यूलिंग शामिल है, बस एक स्ट्रिंग को विभाजित करने के लिए ! और एक तुम खुद से जुड़ गए !! आप अजगर में विभाजित हो सकते हैं, लेकिन वैसे भी प्रत्येक पैरामीटर को अलग से लिखना आसान है। इसके अलावा, एक सूची साधनों का उपयोग आप विशेष खोल वर्ण से बचने के लिए की जरूरत नहीं है: रिक्त स्थान, ;, >, <, &.. आपका मापदंडों उन वर्ण शामिल कर सकते हैं और आप चिंता करने की ज़रूरत नहीं है! shell=Trueजब तक आप शेल-ओनली कमांड नहीं चला रहे होते हैं , मैं वास्तव में उपयोग करने का कारण नहीं देख सकता ।
nosklo

nosklo, जो होना चाहिए: p = subprocess.Popen (cmd, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
सेंथिल कुमारन

1
@ मैथटिक: मुझे यकीन नहीं है कि आप उन प्रक्रियाओं को अलग-अलग प्रक्रियाओं के रूप में क्यों करेंगे ... आप फ़ाइल सामग्री में कटौती कर सकते हैं और csvमॉड्यूल का उपयोग करके आसानी से अजगर में पहला क्षेत्र निकाल सकते हैं । लेकिन एक उदाहरण के रूप में, अजगर में आपकी पाइप लाइन इस प्रकार होगी: p = Popen(['cut', '-f1'], stdin=open('longfile.tab'), stdout=PIPE) ; p2 = Popen(['head', '-100'], stdin=p.stdout, stdout=PIPE) ; result, stderr = p2.communicate() ; print resultध्यान दें कि आप लंबे फिलामेंट्स और शेल विशेष वर्णों के साथ काम कर सकते हैं बिना भागने के, अब शेल शामिल नहीं है। इसके अलावा यह बहुत तेज है क्योंकि एक कम प्रक्रिया है।
nosklo

11
पायथन 2 के for line in iter(p.stdout.readline, b'')बजाय उपयोग करें for line in p.stdoutअन्यथा वास्तविक समय में लाइनें नहीं पढ़ी जाती हैं, भले ही स्रोत प्रक्रिया अपने आउटपुट को बफर न करे।
JFS

41

मुझे पता है कि यह एक पुराना विषय है, लेकिन अब एक समाधान है। Rsync को विकल्प --outbuf = L के साथ कॉल करें। उदाहरण:

cmd=['rsync', '-arzv','--backup','--outbuf=L','source/','dest']
p = subprocess.Popen(cmd,
                     stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, b''):
    print '>>> {}'.format(line.rstrip())

3
यह काम करता है और ऊपर के सभी संवादों के माध्यम से भविष्य के पाठकों को स्क्रॉल करने से बचाने के लिए इसे बनाया जाना चाहिए।
वेक्टरविक्टर

1
@VectorVictor यह नहीं समझाता है कि क्या चल रहा है, और क्यों चल रहा है। यह हो सकता है कि आपका प्रोग्राम काम करे, जब तक: 1. आप preexec_fn=os.setpgrpप्रोग्राम को उसकी मूल स्क्रिप्ट से बचने के लिए जोड़ते हैं । 2. आप प्रक्रिया के पाइप से पढ़ना छोड़ देते हैं। 3. प्रक्रिया बहुत सारे डेटा को आउटपुट करती है, पाइप को भरती है 4. आप घंटों तक अटके रहते हैं। यह जानने की कोशिश कर रहे हैं कि कुछ यादृच्छिक समय के बाद आप जिस कार्यक्रम को चला रहे हैं, वह क्यों है । @Nosklo के जवाब से मुझे बहुत मदद मिली।
दानुकर

15

लिनक्स पर, मुझे बफरिंग से छुटकारा पाने की समान समस्या थी। मैंने आखिरकार PIPE बफरिंग से छुटकारा पाने के लिए "stdbuf -o0" (या, उम्मीद से असहमत) का इस्तेमाल किया।

proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE)
stdout = proc.stdout

मैं तब stdout पर select.select का उपयोग कर सकता था।

Https://unix.stackexchange.com/questions/25372/ भी देखें


2
पायथन से सी कोड स्टडआउट को हथियाने की कोशिश करने वाले किसी भी व्यक्ति के लिए, मैं पुष्टि कर सकता हूं कि यह समाधान केवल मेरे लिए काम कर रहा था। स्पष्ट होने के लिए, मैं पोपेन में अपनी मौजूदा कमांड सूची में 'stdbuf', '-o0' जोड़ने की बात कर रहा हूं।
लापरवाह

धन्यवाद! वास्तव में pytest / pytest-bdd परीक्षणों के एक समूह के साथ उपयोगी stdbuf -o0साबित हुआ जो मैंने लिखा था कि एक C ++ ऐप को स्पॉन करें और सत्यापित करें कि यह कुछ लॉग स्टेटमेंट का उत्सर्जन करता है। बिना , इन परीक्षणों को C ++ प्रोग्राम से (बफर) आउटपुट प्राप्त करने के लिए 7 सेकंड की आवश्यकता थी। अब वे लगभग तुरंत दौड़ते हैं! stdbuf -o0
evadeflow

11

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

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

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

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

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

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

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


1
आप मेरा दिन बचाते हैं, PYTHONUNBUFFERED = 1
diewland

9
for line in p.stdout:
  ...

हमेशा अगली पंक्ति की फ़ीड तक ब्लॉक करें।

"वास्तविक समय" व्यवहार के लिए आपको कुछ इस तरह करना होगा:

while True:
  inchar = p.stdout.read(1)
  if inchar: #neither empty string nor None
    print(str(inchar), end='') #or end=None to flush immediately
  else:
    print('') #flush for implicit line-buffering
    break

जब बच्चे की प्रक्रिया अपने स्टडआउट को बंद कर देती है या बाहर निकल जाती है, तो लूप-लूप छोड़ दिया जाता है। read()/read(-1)तब तक ब्लॉक रहेगा जब तक कि बाल प्रक्रिया अपने स्टडआउट या बाहर नहीं निकल जाती।


1
incharइसके बजाय कभी Noneउपयोग नहीं किया if not inchar:जाता है ( read()ईओएफ पर खाली स्ट्रिंग लौटाता है)। btw, यह बदतर for line in p.stdoutहै कि पाइथन 2 ( for line in iter (p.stdout.readline, '' ') का उपयोग वास्तविक समय में भी पूरी लाइनों को प्रिंट नहीं कर सकता है।
JFS

1
मैंने इसे ओक्सीक्स पर अजगर 3.4 के साथ परीक्षण किया है, और यह काम नहीं करता है।
QED

1
@qed: for line in p.stdout:पायथन पर काम करता है 3. ''(यूनिकोड स्ट्रिंग) और b''(बाइट्स) के बीच अंतर को समझना सुनिश्चित करें । पाइथन
सबप्रोसेस डॉट कॉम्यूनिकेट

8

आपकी समस्या यह है:

for line in p.stdout:
    print(">>> " + str(line.rstrip()))
    p.stdout.flush()

इट्रेटर में अतिरिक्त बफ़रिंग है।

इस तरह करने की कोशिश करें:

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

5

आपको पाइप से अटूट प्रिंट करने के लिए स्टडआउट नहीं मिल सकता है (जब तक कि आप प्रोग्राम को फिर से प्रिंट करने के लिए नहीं लिखते हैं), तो यहाँ मेरा समाधान है:

Strout को sterrout पर पुनर्निर्देशित करें, जो बफर नहीं है। '<cmd> 1>&2'करना चाहिए। प्रक्रिया इस प्रकार खोलें: myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
आप stdout या stderr से अंतर नहीं कर सकते, लेकिन आपको तुरंत सभी आउटपुट मिलते हैं।

आशा है कि यह किसी को भी इस समस्या से निपटने में मदद करता है।


4
या तुमने कोशिश की? क्योंकि यह काम नहीं करता है .. अगर stdout उस प्रक्रिया में बफ़र किया जाता है, तो इसे उसी तरीके से stderr पर पुनर्निर्देशित नहीं किया जाएगा, जो किसी PIPE या फ़ाइल पर पुनर्निर्देशित नहीं किया गया है ..
फ़िलिप पिना

5
यह सादा गलत है। stdout बफ़रिंग प्रोग्राम के भीतर ही होता है। शेल सिंटैक्स 1>&2सिर्फ परिवर्तन करता है जो फ़ाइल-डिस्क्रिप्टर को प्रोग्राम लॉन्च करने से पहले इंगित करता है। प्रोग्राम स्वयं को स्ट्रेडर ( 1>&2) या इसके विपरीत ( 2>&1) में रीडायरेक्ट करने के बीच अंतर नहीं कर सकता है, इसलिए इससे प्रोग्राम के बफरिंग व्यवहार पर कोई प्रभाव नहीं पड़ेगा। और किसी भी तरह से 1>&2सिंटैक्स की व्याख्या शेल द्वारा की जाती है। subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)निर्दिष्ट नहीं होने के कारण आप विफल होंगे shell=True
विल मनाली

मामले में लोग इसे पढ़ रहे होंगे: मैंने stdout के बजाय stderr का उपयोग करने की कोशिश की, यह ठीक उसी व्यवहार को दर्शाता है।
मार्टिनेटेक्स

3

Rsync प्रक्रिया से स्टैडआउट को परिवर्तित न किया जाए।

p = subprocess.Popen(cmd,
                     shell=True,
                     bufsize=0,  # 0=unbuffered, 1=line-buffered, else buffer-size
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)

3
बफ़रिंग rsync साइड में होता है, अजगर विशेषता पर bufsize विशेषता को बदलने में मदद नहीं करेगा।
nosklo

14
किसी और को खोजने के लिए, नोस्कोलो का जवाब पूरी तरह से गलत है: rsync की प्रगति प्रदर्शन बफर नहीं है; वास्तविक समस्या यह है कि सबप्रोसेस एक फ़ाइल ऑब्जेक्ट देता है और फ़ाइल पुनरावृत्ति इंटरफ़ेस में bufsize = 0 के साथ भी एक खराब प्रलेखित आंतरिक बफ़र होता है, जिससे आपको बफ़र भरने से पहले परिणामों की आवश्यकता होने पर बार-बार रीडलाइन () कॉल करने की आवश्यकता होती है।
क्रिस एडम्स

3

आउटपुट के कैशिंग से बचने के लिए आप pexpect की कोशिश कर सकते हैं,

child = pexpect.spawn(launchcmd,args,timeout=None)
while True:
    try:
        child.expect('\n')
        print(child.before)
    except pexpect.EOF:
        break

पुनश्च : मुझे पता है कि यह प्रश्न बहुत पुराना है, फिर भी मेरे लिए काम करने वाले समाधान प्रदान करता है।

PPS : एक अन्य प्रश्न से यह उत्तर मिला


3
    p = subprocess.Popen(command,
                                bufsize=0,
                                universal_newlines=True)

मैं अजगर में rsync के लिए एक GUI लिख रहा हूं, और एक ही जांच कर रहा हूं। इस समस्या ने मुझे कई दिनों तक परेशान किया, जब तक कि मैंने इसे pyDoc में नहीं पाया।

यदि Universal_newlines सत्य है, तो फ़ाइल ऑब्जेक्ट्स stdout और stderr को सार्वभौमिक newlines मोड में पाठ फ़ाइलों के रूप में खोला जाता है। लाइनों को किसी भी '\ n', यूनिक्स के अंत-लाइन सम्मेलन, '\ r', पुराने मैकिन्टोश सम्मेलन या '\ r \ n', विंडोज सम्मेलन द्वारा समाप्त किया जा सकता है। इन सभी बाहरी अभ्यावेदन को पायथन प्रोग्राम द्वारा '\ n' के रूप में देखा जाता है।

ऐसा लगता है कि अनुवाद चालू होने पर rsync '\ r' आउटपुट करेगा।


1

मैंने देखा है कि मध्यवर्ती के रूप में एक अस्थायी फ़ाइल का उपयोग करने का कोई उल्लेख नहीं है। निम्नलिखित एक अस्थायी फ़ाइल में आउटपुट करके बफरिंग मुद्दों के आसपास हो जाता है और आपको pty से कनेक्ट किए बिना rsync से आने वाले डेटा को पार्स करने की अनुमति देता है। मैंने एक लिनक्स बॉक्स पर निम्नलिखित का परीक्षण किया, और rsync का आउटपुट प्लेटफ़ॉर्म पर भिन्न होता है, इसलिए आउटपुट को पार्स करने के लिए नियमित भाव भिन्न हो सकते हैं:

import subprocess, time, tempfile, re

pipe_output, file_name = tempfile.TemporaryFile()
cmd = ["rsync", "-vaz", "-P", "/src/" ,"/dest"]

p = subprocess.Popen(cmd, stdout=pipe_output, 
                     stderr=subprocess.STDOUT)
while p.poll() is None:
    # p.poll() returns None while the program is still running
    # sleep for 1 second
    time.sleep(1)
    last_line =  open(file_name).readlines()
    # it's possible that it hasn't output yet, so continue
    if len(last_line) == 0: continue
    last_line = last_line[-1]
    # Matching to "[bytes downloaded]  number%  [speed] number:number:number"
    match_it = re.match(".* ([0-9]*)%.* ([0-9]*:[0-9]*:[0-9]*).*", last_line)
    if not match_it: continue
    # in this case, the percentage is stored in match_it.group(1), 
    # time in match_it.group(2).  We could do something with it here...

यह वास्तविक समय में नहीं है। कोई फ़ाइल rsync के पक्ष में बफ़रिंग समस्या को हल नहीं करता है।
जफ


3
while not p.poll()अनंत लूप की ओर जाता है यदि p.poll() is None
सबप्रोसेस

Windows पहले से ही खोली गई फ़ाइल को खोलने से मना कर सकता है, इसलिए open(file_name)विफल हो सकता है
jfs

1
मुझे बस यह उत्तर मिला, दुर्भाग्य से केवल लिनक्स के लिए, लेकिन एक आकर्षण लिंक की तरह काम करता है इसलिए मैं सिर्फ अपनी कमांड को इस प्रकार बढ़ाता हूं: command_argv = ["stdbuf","-i0","-o0","-e0"] + command_argvऔर कॉल करें: popen = subprocess.Popen(cmd, stdout=subprocess.PIPE) और अब मैं बिना किसी बफरिंग से पढ़ सकता हूं
अरविद टेरिजाबेस्चियन

0

यदि आप एक धागे में कुछ इस तरह से चलाते हैं और एक विधि की संपत्ति में ffmpeg_time संपत्ति को बचाते हैं, तो आप इसे एक्सेस कर सकते हैं, यह बहुत अच्छा काम करेगा मुझे इस तरह से आउटपुट मिलते हैं: आउटपुट ऐसा होता है यदि आप टिंकर में थ्रेडिंग का उपयोग करते हैं

input = 'path/input_file.mp4'
output = 'path/input_file.mp4'
command = "ffmpeg -y -v quiet -stats -i \"" + str(input) + "\" -metadata title=\"@alaa_sanatisharif\" -preset ultrafast -vcodec copy -r 50 -vsync 1 -async 1 \"" + output + "\""
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=True)
for line in self.process.stdout:
    reg = re.search('\d\d:\d\d:\d\d', line)
    ffmpeg_time = reg.group(0) if reg else ''
    print(ffmpeg_time)

-1

पायथन 3 में, यहां एक समाधान दिया गया है, जो कमांड लाइन से एक कमांड लेता है और प्राप्त होते ही वास्तविक समय में अच्छी तरह से डिकोड किए गए तारों को बचाता है।

रिसीवर ( receiver.py):

import subprocess
import sys

cmd = sys.argv[1:]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
for line in p.stdout:
    print("received: {}".format(line.rstrip().decode("utf-8")))

उदाहरण सरल प्रोग्राम जो वास्तविक समय आउटपुट उत्पन्न कर सकता है ( dummy_out.py):

import time
import sys

for i in range(5):
    print("hello {}".format(i))
    sys.stdout.flush()  
    time.sleep(1)

आउटपुट:

$python receiver.py python dummy_out.py
received: hello 0
received: hello 1
received: hello 2
received: hello 3
received: hello 4
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.