रूबी में बाहरी प्रक्रिया के STDOUT से लगातार पढ़ा जाता है


86

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

मुझे प्रतीत नहीं हो सकता है कि प्रगति संदेश ब्लेंडर सामान्य रूप से शेल पर प्रिंट करता है जब ब्लेंडर प्रक्रिया अभी भी चल रही है, और मैंने कुछ तरीके आजमाए हैं। मैं हमेशा ब्लेंडर छोड़ने के बाद ब्लेंडर के स्टैडआउट का उपयोग करने लगता हूं , जबकि यह अभी भी चल रहा है।

यहाँ एक असफल प्रयास का एक उदाहरण है। यह ब्लेंडर के आउटपुट की पहली 25 लाइनों को प्राप्त करता है और प्रिंट करता है, लेकिन ब्लेंडर की प्रक्रिया समाप्त होने के बाद ही:

blender = nil
t = Thread.new do
  blender = open "| blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1"
end
puts "Blender is doing its job now..."
25.times { puts blender.gets}

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

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

जवाबों:


175

मुझे इस समस्या को हल करने में कुछ सफलता मिली है। यहां कुछ विवरणों के साथ विवरण दिए गए हैं, अगर किसी को भी इसी तरह की समस्या है तो यह पृष्ठ ढूंढता है। लेकिन अगर आप विवरणों की परवाह नहीं करते हैं, तो इसका संक्षिप्त उत्तर है :

निम्नलिखित तरीके से PTY.spawn का उपयोग करें (अपने स्वयं के आदेश के साथ):

require 'pty'
cmd = "blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1" 
begin
  PTY.spawn( cmd ) do |stdout, stdin, pid|
    begin
      # Do stuff with the output here. Just printing to show it works
      stdout.each { |line| print line }
    rescue Errno::EIO
      puts "Errno:EIO error, but this probably just means " +
            "that the process has finished giving output"
    end
  end
rescue PTY::ChildExited
  puts "The child process exited!"
end

और यहाँ लंबा जवाब है , जिस तरह से बहुत सारे विवरण के साथ:

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

लेकिन जब आप इन प्रक्रियाओं को शेल से चलाते हैं, तो वे वास्तविक समय में शेल के लिए stdout प्रदर्शित करते हैं, और stdout बफ़र्ड प्रतीत नहीं होता है। जब मुझे विश्वास होता है कि किसी अन्य प्रक्रिया से कॉल किया जाता है, तो यह केवल बफ़र है, लेकिन यदि किसी शेल से निपटा जा रहा है, तो वास्तविक समय में, स्टडआउट को अप्रभावित देखा जाता है।

इस व्यवहार को एक रूबी प्रक्रिया के साथ भी देखा जा सकता है क्योंकि बाल प्रक्रिया जिसका उत्पादन वास्तविक समय में एकत्र किया जाना चाहिए। बस एक स्क्रिप्ट बनाएं, random.rb, निम्न पंक्ति के साथ:

5.times { |i| sleep( 3*rand ); puts "#{i}" }

फिर इसे बुलाने और इसके आउटपुट को वापस करने के लिए एक रूबी स्क्रिप्ट:

IO.popen( "ruby random.rb") do |random|
  random.each { |line| puts line }
end

आप देखेंगे कि आपको वास्तविक समय में परिणाम नहीं मिल रहा है जैसा कि आप उम्मीद कर सकते हैं, लेकिन बाद में सभी एक बार। STDOUT को बफ़र किया जा रहा है, भले ही आप random.rb को स्वयं चलाते हों, लेकिन यह बफ़र्ड नहीं है। इसे STDOUT.flushrandom.rb में ब्लॉक के अंदर एक स्टेटमेंट जोड़कर हल किया जा सकता है । लेकिन अगर आप स्रोत नहीं बदल सकते हैं, तो आपको इसके आसपास काम करना होगा। आप इसे प्रक्रिया से बाहर नहीं निकाल सकते।

यदि सबप्रोसेस वास्तविक समय में शेल में प्रिंट कर सकते हैं, तो रूबी के साथ वास्तविक समय में भी इसे कैप्चर करने का एक तरीका होना चाहिए। और वहां है। आपको पीटीवाई मॉड्यूल का उपयोग करना होगा, जो रूबी कोर में शामिल है, मुझे विश्वास है (1.8.6 वैसे)। दुखद बात यह है कि यह प्रलेखित नहीं है। लेकिन मुझे सौभाग्य से उपयोग के कुछ उदाहरण मिले।

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

बच्चे के रूप में random.rb स्क्रिप्ट के साथ इस काम को करने के लिए, निम्न कोड का प्रयास करें:

require 'pty'
begin
  PTY.spawn( "ruby random.rb" ) do |stdout, stdin, pid|
    begin
      stdout.each { |line| print line }
    rescue Errno::EIO
    end
  end
rescue PTY::ChildExited
  puts "The child process exited!"
end

7
यह बहुत अच्छा है, लेकिन मेरा मानना ​​है कि स्टड और स्टडआउट ब्लॉक मापदंडों को स्वैप किया जाना चाहिए। देखें: ruby-doc.org/stdlib-1.9.3/libdoc/pty/rdoc/…
माइक कॉनिग्लियारो

1
पेंटी को कैसे बंद करें? पीड़ को मार डालो?
बोरिस बी।

बहुत बढ़िया जवाब। आपने मुझे अपने रेक की स्क्रिप्ट को हेरोकू के लिए बेहतर बनाने में मदद की। यह प्रदर्शित करता है 'Git पुश' वास्तविक समय में लॉग और कार्य रोकता है, तो 'घातक:' पाया gist.github.com/sseletskyy/9248357
सर्ज Seletskyy

1
मैंने मूल रूप से इस पद्धति का उपयोग करने की कोशिश की है, लेकिन 'pty' विंडोज में उपलब्ध नहीं है। जैसा कि यह पता चला है, STDOUT.sync = trueकि सभी की जरूरत है (नीचे mveerman का जवाब)। यहाँ कुछ उदाहरण कोड के साथ एक और धागा है
पाकमन

12

का उपयोग करें IO.popenयह एक अच्छा उदाहरण है।

आपका कोड कुछ इस तरह बन जाएगा:

blender = nil
t = Thread.new do
  IO.popen("blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1") do |blender|
    blender.each do |line|
      puts line
    end
  end
end

मैंने यह कोशिश की है। समस्या वही है। मुझे आउटपुट बाद में मिलता है। मेरा मानना ​​है कि IO.popen कमांड के रूप में पहला तर्क चलाकर शुरू करता है, और इसके समाप्त होने का इंतजार करता है। मेरे मामले में, आउटपुट ब्लेंडर द्वारा दिया जाता है जबकि ब्लेंडर अभी भी प्रसंस्करण कर रहा है। और उसके बाद ब्लॉक को लागू किया जाता है, जो मेरी मदद नहीं करता है।
एहसानुल

यहाँ मैंने कोशिश की है। यह ब्लेंडर होने के बाद आउटपुट देता है: IO.popen ("ब्लेंडर -b mball.blend // renders / -F JPEG -x 1 -f 1", "w +") करना | ब्लेंडर | blender.each {| लाइन | लाइन डालता है; आउटपुट + = लाइन;} अंत
एहसानुल

3
मुझे यकीन नहीं है कि आपके मामले में क्या हो रहा है। मैंने ऊपर दिए गए कोड का परीक्षण किया yes, एक कमांड-लाइन एप्लिकेशन जो कभी समाप्त नहीं होता है , और यह काम करता है। कोड निम्नानुसार था IO.popen('yes') { |p| p.each { |f| puts f } }:। मुझे संदेह है कि यह कुछ ऐसा है जो ब्लेंडर के साथ करना है, और रूबी नहीं। संभवतः ब्लेंडर हमेशा अपने STDOUT को फ्लश नहीं करता है।
सिवन तैफूर

ठीक है, मैंने इसे परीक्षण करने के लिए एक बाहरी रूबी प्रक्रिया के साथ कोशिश की, और आप सही हैं। एक ब्लेंडर मुद्दा लगता है। वैसे भी उत्तर के लिए धन्यवाद।
एहसानुल

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


4

ब्लेंडर शायद कार्यक्रम को समाप्त करने तक लाइन-ब्रेक को प्रिंट नहीं करता है। इसके बजाय, यह कैरेज़ रिटर्न कैरेक्टर (\ r) को प्रिंट कर रहा है। सबसे आसान समाधान संभवतः जादू विकल्प की खोज कर रहा है जो प्रगति संकेतक के साथ लाइन-ब्रेक को प्रिंट करता है।

समस्या यह है कि IO#gets(और विभिन्न अन्य IO विधियाँ) रेखा विराम को एक सीमांकक के रूप में उपयोग करती हैं। वे स्ट्रीम को तब तक पढ़ेंगे जब तक कि वे "\ n" अक्षर को हिट न कर दें (जो ब्लेंडर नहीं भेज रहा है)।

इनपुट विभाजक सेट करने $/ = "\r"या blender.gets("\r")इसके बजाय का उपयोग करने का प्रयास करें ।

BTW, इन जैसी समस्याओं के लिए, आपको हमेशा स्ट्रिंग के भीतर किसी भी छिपे हुए चरित्र को देखने के लिए ( puts someobj.inspectया p someobjदोनों जो एक ही काम करते हैं) देखना चाहिए।


1
मैंने अभी दिए गए आउटपुट का निरीक्षण किया, और ऐसा लगता है कि ब्लेंडर एक लाइन ब्रेक (\ n) का उपयोग करता है, इसलिए यह मुद्दा नहीं था। वैसे भी टिप के लिए धन्यवाद, मुझे लगता है कि अगली बार मैं कुछ इस तरह से डिबगिंग कर रहा हूँ ध्यान में रखना होगा।
एहसानुल

0

मुझे नहीं पता कि उस समय एहसानुल ने सवाल का जवाब दिया था Open3::pipeline_rw()या नहीं, लेकिन यह अभी तक उपलब्ध था , लेकिन यह वास्तव में चीजों को सरल बनाता है।

मैं ब्लेंडर के साथ ehsanul का काम समझ में नहीं आता, तो मैं के साथ एक और उदाहरण बनाया tarऔर xztarstdout स्ट्रीम में इनपुट फ़ाइल (s) जोड़ेंगे, फिर उसे xzले लें stdoutऔर फिर से, दूसरे stdout में संपीड़ित करें। हमारा काम आखिरी स्टडआउट लेना है और इसे हमारी अंतिम फ़ाइल में लिखना है:

require 'open3'

if __FILE__ == $0
    cmd_tar = ['tar', '-cf', '-', '-T', '-']
    cmd_xz = ['xz', '-z', '-9e']
    list_of_files = [...]

    Open3.pipeline_rw(cmd_tar, cmd_xz) do |first_stdin, last_stdout, wait_threads|
        list_of_files.each { |f| first_stdin.puts f }
        first_stdin.close

        # Now start writing to target file
        open(target_file, 'wb') do |target_file_io|
            while (data = last_stdout.read(1024)) do
                target_file_io.write data
            end
        end # open
    end # pipeline_rw
end

0

पुराना सवाल है, लेकिन ऐसी ही समस्याएं थीं।

वास्तव में मेरे रूबी कोड को बदलने के बिना, एक चीज जिसने मेरे पाइप को stdbuf के साथ लपेटने में मदद की थी , जैसे:

cmd = "stdbuf -oL -eL -i0  openssl s_client -connect #{xAPI_ADDRESS}:#{xAPI_PORT}"

@xSess = IO.popen(cmd.split " ", mode = "w+")  

मेरे उदाहरण में, मैं जिस वास्तविक कमांड के साथ बातचीत करना चाहता हूं, जैसे कि यह एक शेल था, ओपनसेल है

-oL -eL यह बताएं कि STDOUT और STDERR को केवल एक नई सीमा तक बफर करें। बदलें Lके साथ 0पूरी तरह से unbuffer करने के लिए।

यह हमेशा काम नहीं करता है, हालांकि: कभी-कभी लक्ष्य प्रक्रिया अपने स्वयं के स्ट्रीम बफर प्रकार को लागू करती है, जैसे एक और उत्तर बताया गया है।

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