รับเอาต์พุตของการเรียก system () ใน Ruby


309

ถ้าฉันเรียกคำสั่งโดยใช้Kernel # systemใน Ruby ฉันจะรับเอาต์พุตได้อย่างไร

system("ls")

1
คุณอาจต้องการดูหัวข้อนี้ใน comp.lang.ruby
Manrico Corazzi

นี่เป็นด้ายมือมากขอบคุณ คลาสสำหรับการรันคำสั่งและการรับความคิดเห็นนั้นยอดเยี่ยมในโค้ดตัวอย่าง
ylluminate

3
สำหรับชาว Google ในอนาคต หากคุณต้องการเรียนรู้เกี่ยวกับการเรียกคำสั่งระบบอื่น ๆ และความแตกต่างของพวกเขาดูคำตอบ SOนี้
Uzbekjon

คำตอบ:


347

ฉันต้องการที่จะขยายและชี้แจงคำตอบของความสับสนวุ่นวายเล็กน้อย

หากคุณล้อมรอบคำสั่งของคุณด้วย backticks แสดงว่าคุณไม่จำเป็นต้องมีระบบการโทร (อย่างชัดเจน) () เลย backticks ดำเนินการคำสั่งและส่งกลับผลลัพธ์เป็นสตริง จากนั้นคุณสามารถกำหนดค่าให้กับตัวแปรดังนี้:

output = `ls`
p output

หรือ

printf output # escapes newline chars

4
ถ้าฉันต้องการให้ตัวแปรเป็นส่วนหนึ่งของคำสั่งของฉัน นั่นคือสิ่งที่ต้องการระบบ ("ls" + ชื่อไฟล์) แปลว่าเมื่อใดที่จะใช้ backticks
Vijay Dev

47
คุณสามารถทำการประเมินผลนิพจน์ได้เช่นเดียวกับที่คุณใช้กับสตริงปกติ: ls #{filename}.
Craig Walker

36
คำตอบนี้ไม่แนะนำให้เลือก: จะแนะนำปัญหาใหม่ของการป้อนข้อมูลของผู้ใช้ที่ไม่ได้รับอนุญาต
Dogweather

4
@Dogweather: นั่นอาจเป็นจริง แต่มันแตกต่างจากวิธีอื่นใดไหม?
Craig Walker

20
ถ้าคุณต้องการ capure stderr เพียงแค่ใส่ 2> & 1 ที่ส่วนท้ายของคำสั่งของคุณ เช่น output =command 2>&1
micred

243

โปรดทราบว่าการแก้ปัญหาทั้งหมดที่คุณผ่านสตริงที่มีค่าให้กับผู้ใช้system, %x[]ฯลฯ ที่ไม่ปลอดภัย! จริง ๆ แล้วไม่ปลอดภัยหมายถึง: ผู้ใช้อาจเรียกใช้รหัสเพื่อเรียกใช้ในบริบทและด้วยสิทธิ์ทั้งหมดของโปรแกรม

เท่าที่ฉันสามารถพูดได้เท่านั้นsystemและOpen3.popen3มีตัวแปรความปลอดภัย / การหลบหนีใน Ruby 1.8 ใน Ruby 1.9 IO::popenยังยอมรับอาร์เรย์

เพียงส่งผ่านตัวเลือกและอาร์กิวเมนต์เป็นอาร์เรย์ไปยังหนึ่งในการโทรเหล่านี้

หากคุณต้องการไม่ใช่เพียงแค่สถานะออก แต่ยังเป็นผลลัพธ์ที่คุณอาจต้องการใช้Open3.popen3:

require 'open3'
stdin, stdout, stderr, wait_thr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets(nil)
stdout.close
stderr.gets(nil)
stderr.close
exit_code = wait_thr.value

หมายเหตุว่ารูปแบบบล็อกจะปิดอัตโนมัติ stdin, stdout และ stderr- มิฉะนั้นพวกเขาจะต้องถูกปิดอย่างชัดเจน

ข้อมูลเพิ่มเติมที่นี่: การสร้างคำสั่งอนามัยเชลล์หรือการเรียกระบบใน Ruby


26
นี่เป็นคำตอบเดียวที่ตอบคำถามและแก้ปัญหาได้โดยไม่ต้องแนะนำคำถามใหม่ (อินพุตที่ไม่ถูกแก้ไข)
Dogweather

2
ขอบคุณ! นี่คือคำตอบที่ฉันหวังไว้ การแก้ไขอย่างใดอย่างหนึ่ง: การgetsเรียกควรผ่านการโต้แย้งnilเช่นเดียวกับที่เราเพิ่งได้รับบรรทัดแรกของผลลัพธ์ stdout.gets(nil)ดังนั้นเช่น
เกร็กราคา

3
stdin, stdout และ stderr ควรจะปิดอย่างชัดเจนในรูปแบบบล็อกที่ไม่ใช่
Yarin

มีใครรู้บ้างไหมว่ามีอะไรเปลี่ยนแปลงใน Ruby 2.0 หรือ 2.1 การแก้ไขหรือความคิดเห็นจะได้รับการชื่นชม ;-)
Simon Hürlimann

1
ผมคิดว่าการอภิปรายที่อยู่รอบ ๆOpen3.popen3จะหายไปเป็นปัญหาใหญ่: ถ้าคุณมีกระบวนการย่อยที่เขียนข้อมูลเพิ่มเติมเพื่อ stdout กว่าท่อสามารถถือเป็นกระบวนการย่อยได้รับการระงับในและโปรแกรมของคุณได้รับในการติดstderr.write stdout.gets(nil)
hagello

165

เพียงบันทึกถ้าคุณต้องการทั้ง (เอาท์พุทและผลการดำเนินงาน) คุณสามารถทำได้:

output=`ls no_existing_file` ;  result=$?.success?

4
นี่คือสิ่งที่ฉันกำลังมองหา ขอบคุณ.
jdl

12
ที่จับ stdout เท่านั้นและ stderr ไปที่คอนโซล เพื่อรับ stderr ใช้: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept

8
คำตอบนี้ไม่ปลอดภัยและไม่ควรใช้ - หากคำสั่งนั้นเป็นค่าคงที่ดังนั้นไวยากรณ์ backtick น่าจะทำให้เกิดบั๊กซึ่งอาจเป็นจุดอ่อนด้านความปลอดภัย (และแม้ว่าจะเป็นค่าคงที่ก็อาจจะทำให้ใครบางคนใช้มันสำหรับค่าคงที่ในภายหลังและทำให้เกิดข้อผิดพลาด) ดูคำตอบของ Simon Hürlimannสำหรับวิธีแก้ไขที่ถูกต้อง
เกร็กราคา

23
รุ่งโรจน์ถึง Greg Price เพื่อทำความเข้าใจเกี่ยวกับความจำเป็นในการหลีกเลี่ยงการป้อนข้อมูลผู้ใช้ แต่ไม่ถูกต้องที่จะพูดคำตอบนี้ตามที่เขียนไว้ไม่ปลอดภัย วิธีการที่กล่าวถึงใน Open3 นั้นมีความซับซ้อนมากขึ้นและมีการพึ่งพามากขึ้นและการโต้เถียงที่ใครบางคนจะ "ใช้มันสำหรับค่าคงที่ในภายหลัง" นั้นเป็นคนทำฟาง จริงอยู่คุณอาจจะไม่ใช้มันในแอพ Rails แต่สำหรับยูทิลิตี้สคริปต์ระบบที่ไม่มีความน่าเชื่อถือของข้อมูลผู้ใช้ที่ไม่น่าเชื่อถือ backticks นั้นดีมากและไม่มีใครควรรู้สึกแย่กับการใช้มัน
sbeam

69

วิธีที่ตรงไปตรงมาจะทำเช่นนี้ได้อย่างถูกต้องและปลอดภัยคือการใช้งานOpen3.capture2(), Open3.capture2e()หรือOpen3.capture3()หรือ

การใช้ backticks ของ ruby ​​และ%xalias นั้นไม่ปลอดภัยภายใต้สถานการณ์ใด ๆหากใช้กับข้อมูลที่ไม่น่าเชื่อถือ มันเป็นอันตรายธรรมดาและเรียบง่าย:

untrusted = "; date; echo"
out = `echo #{untrusted}`                              # BAD

untrusted = '"; date; echo"'
out = `echo "#{untrusted}"`                            # BAD

untrusted = "'; date; echo'"
out = `echo '#{untrusted}'`                            # BAD

systemฟังก์ชั่นในทางตรงกันข้ามหนีข้อโต้แย้งอย่างถูกต้องถ้าใช้อย่างถูกต้อง :

ret = system "echo #{untrusted}"                       # BAD
ret = system 'echo', untrusted                         # good

ปัญหาคือมันส่งคืนรหัสออกแทนผลลัพธ์และการจับภาพหลังนั้นซับซ้อนและยุ่งเหยิง

คำตอบที่ดีที่สุดในหัวข้อนี้กล่าวถึง Open3 แต่ไม่ใช่ฟังก์ชั่นที่เหมาะสมที่สุดสำหรับงาน Open3.capture2, capture2eและcapture3การทำงานเหมือนsystemแต่ผลตอบแทนที่สองหรือสามข้อโต้แย้ง:

out, err, st = Open3.capture3("echo #{untrusted}")     # BAD
out, err, st = Open3.capture3('echo', untrusted)       # good
out_err, st  = Open3.capture2e('echo', untrusted)      # good
out, st      = Open3.capture2('echo', untrusted)       # good
p st.exitstatus

อีกเรื่องที่กล่าวIO.popen()มา ไวยากรณ์สามารถเงอะงะในแง่ที่ว่ามันต้องการอาร์เรย์เป็นอินพุท แต่ก็ใช้งานได้เช่นกัน:

out = IO.popen(['echo', untrusted]).read               # good

เพื่อความสะดวกคุณสามารถห่อOpen3.capture3()ในฟังก์ชั่นเช่น:

#
# Returns stdout on success, false on failure, nil on error
#
def syscall(*cmd)
  begin
    stdout, stderr, status = Open3.capture3(*cmd)
    status.success? && stdout.slice!(0..-(1 + $/.size)) # strip trailing eol
  rescue
  end
end

ตัวอย่าง:

p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')

ให้ผลตอบแทนต่อไปนี้:

nil
nil
false
false
/usr/bin/which         <— stdout from system('which', 'which')
true                   <- p system('which', 'which')
"/usr/bin/which"       <- p syscall('which', 'which')

2
นี่คือคำตอบที่ถูกต้อง นอกจากนี้ยังเป็นข้อมูลมากที่สุด สิ่งเดียวที่ขาดหายไปคือคำเตือนเกี่ยวกับการปิด std * s ดูนี้ความคิดเห็นอื่น ๆ : require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } หมายเหตุว่ารูปแบบบล็อกจะปิดอัตโนมัติ stdin, stdout และ stderr- มิฉะนั้นพวกเขาจะต้องถูกปิดอย่างชัดเจน
Peter H. Boling

@ PeterH.Boling: ที่ดีที่สุดที่ผมทราบที่capture2, capture2eและcapture3ยังอยู่ใกล้พวกเขา std * s โดยอัตโนมัติ (อย่างน้อยที่สุดฉันไม่เคยพบปัญหาในตอนท้ายของฉัน)
เดนิสเดอเบอร์นาร์

โดยไม่ต้องใช้แบบฟอร์มการบล็อกไม่มีทางที่ codebase จะรู้ได้เมื่อมีบางสิ่งที่ควรปิดดังนั้นฉันจึงสงสัยอย่างมากว่าพวกเขากำลังถูกปิด คุณอาจไม่เคยเจอปัญหาเพราะการไม่ปิดมันจะไม่ทำให้เกิดปัญหาในช่วงสั้น ๆ และถ้าคุณเริ่มกระบวนการที่ใช้เวลานานบ่อยๆพอ otto จะไม่ปรากฏขึ้นที่นั่นถ้าคุณเปิด std * s ห่วง Linux มีขีด จำกัด ของตัวอธิบายไฟล์สูงซึ่งคุณสามารถกดได้ แต่จนกว่าคุณจะกดปุ่มคุณจะไม่เห็น "บั๊ก"
Peter H. Boling

2
@ PeterH.Boling: ไม่ไม่ดูรหัสต้นฉบับ ฟังก์ชั่นนี้เป็นเพียงห่อรอบOpen3#popen2, popen2eและpopen3มีบล็อกที่กำหนดไว้ล่วงหน้า: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
เดนิสเดอ Bernardy

1
@Dennis de Barnardy บางทีคุณอาจพลาดไปว่าฉันเชื่อมโยงไปยังเอกสารระดับเดียวกัน (แม้ว่า Ruby 2.0.0 และวิธีการอื่น ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/ ...... จากตัวอย่าง : `` `stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = wait_thr [: pid] # pid ของกระบวนการเริ่มต้น ... stdin.close # stdin , stdout และ stderr ควรปิดอย่างชัดเจนในรูปแบบนี้ stdout.close stderr.close `` `ฉันเพิ่งจะอ้างถึงเอกสาร" # stdin, stdout และ stderr ควรปิดอย่างชัดเจนในรูปแบบนี้ "
Peter H. Boling

61

คุณสามารถใช้ระบบ () หรือ% x [] ขึ้นอยู่กับผลลัพธ์ที่คุณต้องการ

ระบบ () กลับจริงถ้าคำสั่งพบและวิ่งได้สำเร็จเป็นเท็จอย่างอื่น

>> s = system 'uptime'
10:56  up 3 days, 23:10, 2 users, load averages: 0.17 0.17 0.14
=> true
>> s.class
=> TrueClass
>> $?.class
=> Process::Status

% x [.. ] ในทางกลับกันจะบันทึกผลลัพธ์ของคำสั่งเป็นสตริง:

>> result = %x[uptime]
=> "13:16  up 4 days,  1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> p result 
"13:16  up 4 days,  1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> result.class
=> String

Th บล็อกโพสต์โดยเจทุ่งอธิบายรายละเอียดความแตกต่างระหว่างการใช้ระบบบริหารและ% x [ .. ]


2
ขอบคุณสำหรับเคล็ดลับการใช้% x [] มันแก้ไขปัญหาที่ฉันมีเมื่อฉันใช้เครื่องหมายขีดย้อนกลับในสคริปต์ทับทิมใน Mac OS X เมื่อเรียกใช้สคริปต์เดียวกันบนเครื่อง Windows ด้วย Cygwin มันล้มเหลวเนื่องจากเครื่องหมายขีดหลัง แต่ทำงานกับ% x []
Henrik Warne

22

หากคุณต้องการหลีกเลี่ยงการขัดแย้งใน Ruby 1.9 IO.popenก็ยอมรับอาร์เรย์เช่นกัน

p IO.popen(["echo", "it's escaped"]).read

ในเวอร์ชันก่อนหน้าคุณสามารถใช้Open3.popen3 :

require "open3"

Open3.popen3("echo", "it's escaped") { |i, o| p o.read }

หากคุณต้องผ่านการ stdin สิ่งนี้ควรใช้ได้ทั้ง 1.9 และ 1.8:

out = IO.popen("xxd -p", "r+") { |io|
    io.print "xyz"
    io.close_write
    io.read.chomp
}
p out # "78797a"

ขอบคุณ! มันสมบูรณ์แบบ
เกร็กราคา

21

คุณใช้ backticks:

`ls`

5
การตีกลับไม่ได้สร้างเอาต์พุตที่เทอร์มินัล
พ.ค.

3
ไม่สร้าง stderr แต่ให้ stdout
Nickolay Kondratenko

1
มันไม่ได้เขียนถึง stdout หรือ stderr ลองตัวอย่างนี้ruby -e '%x{ls}'- หมายเหตุไม่มีผลลัพธ์ (fyi %x{}เทียบเท่ากับ backticks)
ocodo

มันใช้งานได้ดีมาก การใช้shจะสะท้อนเอาต์พุตไปยังคอนโซล (เช่น STDOUT) และส่งคืน สิ่งนี้ไม่ได้
Joshua Pinter

19

อีกวิธีคือ:

f = open("|ls")
foo = f.read()

โปรดทราบว่าเป็นอักขระ "ไปป์" ก่อน "ls" ในการเปิด นอกจากนี้ยังสามารถใช้เพื่อป้อนข้อมูลลงในอินพุตมาตรฐานของโปรแกรมรวมทั้งอ่านเอาต์พุตมาตรฐาน


เพียงใช้สิ่งนี้เพื่ออ่านเอาต์พุตมาตรฐานจากคำสั่ง aws cli เพื่ออ่าน json และไม่ใช่ค่าส่งคืนอย่างเป็นทางการของ 'true'
kraftydevil

14

ฉันพบว่าข้อมูลต่อไปนี้มีประโยชน์หากคุณต้องการค่าส่งคืน:

result = %x[ls]
puts result

ฉันต้องการแสดงรายการ pids ของกระบวนการ Java ทั้งหมดในเครื่องของฉันและใช้สิ่งนี้:

ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]

มันเป็นทางออกที่ดี
Ronan Louarn

13

ขณะที่ไซมอนHürlimannอธิบายแล้ว , Open3ปลอดภัยกว่า backticks ฯลฯ

require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }

หมายเหตุว่ารูปแบบบล็อกจะปิดอัตโนมัติ stdin, stdout และ stderr- มิฉะนั้นพวกเขาจะต้องถูกปิดอย่างชัดเจน


9

ในขณะที่ใช้ backticks หรือ popen มักเป็นสิ่งที่คุณต้องการ แต่ก็ไม่ได้ตอบคำถามที่ถาม อาจมีเหตุผลที่ถูกต้องสำหรับการบันทึกsystemผลลัพธ์ (อาจใช้สำหรับการทดสอบอัตโนมัติ) Googling เล็กน้อยตอบคำถามฉันคิดว่าฉันจะโพสต์ที่นี่เพื่อประโยชน์ของผู้อื่น

เนื่องจากฉันต้องการสิ่งนี้สำหรับการทดสอบตัวอย่างของฉันใช้การตั้งค่าบล็อกเพื่อจับเอาท์พุทมาตรฐานเนื่องจากการsystemโทรจริงถูกฝังอยู่ในรหัสที่กำลังทดสอบ:

require 'tempfile'

def capture_stdout
  stdout = $stdout.dup
  Tempfile.open 'stdout-redirect' do |temp|
    $stdout.reopen temp.path, 'w+'
    yield if block_given?
    $stdout.reopen stdout
    temp.read
  end
end

เมธอดนี้รวบรวมเอาต์พุตใด ๆ ในบล็อกที่กำหนดโดยใช้ tempfile เพื่อเก็บข้อมูลจริง ตัวอย่างการใช้งาน:

captured_content = capture_stdout do
  system 'echo foo'
end
puts captured_content

คุณสามารถเปลี่ยนการโทรที่มีสิ่งที่เรียกร้องภายในsystem systemคุณสามารถใช้วิธีการที่คล้ายกันเพื่อจับภาพstderrหากคุณต้องการ


8

หากคุณต้องการให้ผลลัพธ์เปลี่ยนเส้นทางไปยังไฟล์โดยใช้Kernel#systemคุณสามารถแก้ไข descriptor ดังนี้:

เปลี่ยนเส้นทาง stdout และ stderr ไปยังไฟล์ (/ tmp / log) ในโหมดต่อท้าย:

system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])

สำหรับคำสั่งที่รันนานสิ่งนี้จะเก็บเอาท์พุทแบบเรียลไทม์ คุณสามารถเก็บเอาท์พุทโดยใช้ IO.pipe และเปลี่ยนเส้นทางจากระบบเคอร์เนล



0

ฉันไม่พบอันนี้ที่นี่เพื่อเพิ่มเข้าไปฉันมีปัญหาบางอย่างในการรับเอาต์พุตเต็มรูปแบบ

คุณสามารถเปลี่ยนเส้นทาง STDERR ไปยัง STDOUT หากคุณต้องการจับภาพ STDERR โดยใช้ backtick

output = `grep hosts / private / etc / * 2> & 1`

แหล่งที่มา: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.