Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

จับ stdout &stderr จากคำสั่งเชลล์ผ่าน Ruby

tl;dr หากคุณต้องการเรียกใช้คำสั่งเชลล์จาก Ruby และจับภาพ stdout, stderr และสถานะการส่งคืน ให้ตรวจสอบ Open3.capture3 กระบวนการ. หากคุณต้องการประมวลผลข้อมูล stdout และ stderr ในรูปแบบการสตรีม โปรดดูที่ Open3.popen3 .

ตัวเลือกที่แย่มากมาย

มี 492 วิธีในการรันคำสั่งเชลล์จาก ruby ​​และแต่ละวิธีทำงานแตกต่างกันเล็กน้อย ฉันพนันได้เลยว่าคุณได้ใช้วิธีใดวิธีหนึ่งด้านล่างนี้ เป้าหมายของฉันคือ back-tick (``) เสมอ

exec("echo 'hello world'") # exits from ruby, then runs the command
system('echo', 'hello world') # returns the status code
sh('echo', 'hello world') # returns the status code
`echo "hello world"` # returns stdout
%x[echo 'hello world'] # returns stdout

แต่แนวทางเหล่านี้ค่อนข้างจำกัด สมมติว่าคุณต้องการจับภาพ stdout ของคำสั่งเชลล์ของคุณไม่เพียงเท่านั้น แต่ยังรวมถึง stderr ของคำสั่งด้วย คุณโชคไม่ดี หรือสมมติว่าคุณต้องการประมวลผลข้อมูล stdout ในสตรีม และไม่ใช่ทั้งหมดในครั้งเดียวเมื่อคำสั่งทำงานเสร็จ โชคไม่ดี

มีอีกทางเลือกหนึ่ง คำสั่งที่ให้คุณสามารถรันคำสั่งแบบอะซิงโครนัสได้ และให้ stdout, stderr, exit codes และ PIDs มาดูกันเลย!

Open3

โมดูล open3 ที่มีชื่อแปลกนี้เป็นส่วนหนึ่งของไลบรารีมาตรฐานของ Ruby มันทำอะไร?

Open3 ให้สิทธิ์คุณในการเข้าถึง stdout, stderr, รหัสออก และเธรดเพื่อรอกระบวนการลูกเมื่อเรียกใช้โปรแกรมอื่น คุณสามารถระบุคุณลักษณะต่างๆ การเปลี่ยนเส้นทาง ไดเร็กทอรีปัจจุบัน ฯลฯ ของโปรแกรมในลักษณะเดียวกับ Process.spawn (_ที่มา:[Open3 Docs](https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html))_

ไม่เคยใช้? ไม่เคยได้ยินเลยเหรอ? ฉันเดาว่าคงเป็นเพราะห้องสมุดไม่เป็นมิตรที่สุด ชื่อตัวเองฟังดูเหมือน C มากกว่า Ruby และเอกสารประกอบค่อนข้างแข็งคอ แต่เมื่อคุณได้ลองใช้ คุณจะพบว่ามันไม่ได้น่ากลัวอย่างที่คิด

จับ3

จะเกิดอะไรขึ้นหากมีวิธีง่ายๆ ในการจับภาพ stdout, stderr และรหัสสถานะ ก็มีนะ หากคุณไม่มีเวลาอ่านส่วนที่เหลือของบทความนี้ แค่รู้ว่าคุณสามารถใช้วิธีการที่เรียกว่าการจับภาพ3 และเรียกมันว่าวัน

ลองมาดูตัวอย่างกัน สมมติว่าคุณต้องการรับรายการไฟล์ในไดเร็กทอรีปัจจุบันของคุณ ในการทำเช่นนั้น คุณสามารถเรียกใช้ ls คำสั่ง

หากคุณจะใช้ไวยากรณ์ back-tick จะมีลักษณะดังนี้:

puts(`ls`)

ด้วย capture3 ดูเหมือนว่า:

require 'open3'
stdout, stderr, status = Open3.capture3("ls")

สิ่งนี้จะเรียกใช้คำสั่งของคุณและให้ stdout และ stderr เป็นสตริง ไม่ยุ่งยาก ไม่ยุ่งยาก

ความปลอดภัย

โดยทั่วไป คุณไม่ต้องการให้ผู้ใช้ของคุณสามารถเรียกใช้คำสั่งตามอำเภอใจบนเว็บเซิร์ฟเวอร์ของคุณได้ นั่นเป็นสาเหตุที่โค้ดชอบ identify #{ params[:filename] } เป็นความคิดที่น่ากลัวมาก

Open3 ช่วยให้คุณหลีกเลี่ยงปัญหาเช่นนี้ได้ด้วยการแยกคำสั่งออกจากข้อมูล มันทำงานเหมือนกับวิธีการของระบบ

Open3.capture3("identify", params[:filename], other_unsafe_params)

popen3

ภายใต้ประทุน capture3 ใช้วิธีที่ทรงพลังกว่าที่เรียกว่า popen3 วิธีนี้ทำงานต่างจากวิธีที่คุ้นเคยเล็กน้อย เช่น system() เล็กน้อย

นี่คือลักษณะ:

require 'open3'
Open3.popen3("ls") do |stdout, stderr, status, thread|
  puts stdout.read
end

มันเหมือนกับเมื่อคุณเปิดและอ่านจากไฟล์ ฉันแน่ใจว่าคุณเคยเห็นโค้ดแบบนี้:

File.open("my/file/path", "r") do |f|
  puts f.read
end

ท่อ

ด้วย Open3 stdout และ stderr เป็นไพพ์ทั้งหมด ซึ่งมีลักษณะเหมือนบัฟเฟอร์ไฟล์มาก และเช่นเดียวกับไฟล์ พวกเขาจำเป็นต้องปิดเมื่อคุณใช้งานเสร็จ นั่นเป็นเหตุผลสำหรับรูปแบบการบล็อก (มีไวยากรณ์ที่ไม่บล็อก แต่คุณต้องโทรปิดด้วยตนเองบน stdout และ stderr)

วิธีการอ่านจะรอจนกว่าท่อจะปิดก่อนที่จะส่งกลับค่า แต่ท่อยังรองรับบรรทัดการอ่านเมื่อมี ลองนึกภาพคำสั่งเชลล์ของคุณใช้เวลาสองสามวินาทีในการรัน ในช่วงเวลานั้น กำลังพิมพ์ข้อความสถานะไปที่ stderr คุณต้องการจับภาพและแสดงต่อผู้ใช้ของคุณ

นี่คือวิธีที่คุณจะจับภาพ stderr ทีละบรรทัด

require 'open3'
Open3.popen3("sleep 2; ls") do |stdout, stderr, status, thread|
  while line=stderr.gets do 
    puts(line) 
  end
end

กระทู้

มีข้อโต้แย้งหนึ่งที่เรายังไม่ได้พูดถึง นั่นมันกระทู้

อาร์กิวเมนต์ thread ให้คุณอ้างอิงถึงเธรด ruby ​​ที่รอคำสั่งของคุณให้เสร็จสิ้น ตอนนี้ คำสั่งไม่ทำงานในเธรด มันทำงานอยู่ในกระบวนการที่แยกจากกันโดยสิ้นเชิง เธรดเพียงแค่เฝ้าดูกระบวนการและรอจนกว่าจะเสร็จสิ้น

คุณสามารถรับข้อมูลที่เป็นประโยชน์จากการอ้างอิงเธรดนั้นได้

  • thread.pid - มีรหัสกระบวนการของคำสั่งเชลล์ของคุณ คุณจะต้องใช้สิ่งนี้หากต้องการดำเนินการระดับระบบปฏิบัติการเพิ่มเติมกับกระบวนการนั้น

  • thread.status - มีสถานะการออกของกระบวนการ 1 หรือ 0 สำหรับความสำเร็จหรือความล้มเหลว

ข้อควรระวัง

จากเอกสาร Open3:

คุณควรระมัดระวังเพื่อหลีกเลี่ยงการชะงักงัน เนื่องจากไพพ์เป็นบัฟเฟอร์ความยาวคงที่[::popen3](https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html#method-c-popen3)(“prog”) {|i, o, e, t| o.read } การหยุดชะงักหากโปรแกรมสร้างเอาต์พุตบน stderr มากเกินไป คุณควรอ่าน stdout และ stderr พร้อมกัน (โดยใช้เธรดหรือ IO.select) อย่างไรก็ตาม หากคุณไม่ต้องการเอาต์พุต stderr คุณสามารถใช้ [::popen2](https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html#method-c- ป๊อป 2). หากการรวม stdout และ stderr output ไม่ใช่ปัญหา คุณสามารถใช้ [::popen2e](https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html#method-c- popen2e). หากคุณต้องการเอาท์พุต stdout และ stderr เป็นสตริงแยกกันจริงๆ คุณสามารถพิจารณา [::capture3](https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html#method-c -capture3).