Nhận đầu ra của các cuộc gọi system () trong Ruby


309

Nếu tôi gọi một lệnh bằng hệ thống Kernel # trong Ruby, làm thế nào để tôi có được đầu ra của nó?

system("ls")


Đây là một chủ đề rất tay, cảm ơn. Lớp để chạy các lệnh và nhận phản hồi là tuyệt vời trong mã mẫu.
ylluminate

3
Đối với nhân viên Google trong tương lai. Nếu bạn muốn tìm hiểu về các lệnh gọi hệ thống khác và sự khác biệt của chúng, hãy xem câu trả lời SO này .
Uzbekjon

Câu trả lời:


347

Tôi muốn mở rộng và làm rõ câu trả lời của sự hỗn loạn một chút.

Nếu bạn bao quanh lệnh của mình bằng backticks, thì bạn hoàn toàn không cần phải gọi hệ thống (). Các backticks thực hiện lệnh và trả lại đầu ra dưới dạng một chuỗi. Sau đó, bạn có thể gán giá trị cho một biến như vậy:

output = `ls`
p output

hoặc là

printf output # escapes newline chars

4
Điều gì xảy ra nếu tôi cần đưa ra một biến như là một phần của lệnh của tôi? Đó là, cái gì đó giống như hệ thống ("ls" + tên tệp) sẽ được dịch thành khi sử dụng backticks?
Vijay Dev

47
Bạn có thể thực hiện đánh giá biểu thức giống như bạn làm với các chuỗi thông thường : ls #{filename}.
Craig Walker

36
Câu trả lời này không được khuyến khích: nó giới thiệu vấn đề mới về đầu vào của người dùng không được xác nhận.
Dogweather

4
@Dogweather: điều đó có thể đúng, nhưng nó có khác gì so với bất kỳ phương pháp nào khác không?
Craig Walker

20
nếu bạn muốn capure stderr, chỉ cần đặt 2> & 1 vào cuối lệnh của bạn. ví dụ: đầu ra =command 2>&1
micred

243

Xin lưu ý rằng tất cả các giải pháp mà bạn chuyển một chuỗi chứa các giá trị do người dùng cung cấp system, %x[]v.v đều không an toàn! Không an toàn thực sự có nghĩa là: người dùng có thể kích hoạt mã để chạy trong ngữ cảnh và với tất cả các quyền của chương trình.

Theo như tôi có thể nói systemvà chỉ Open3.popen3cung cấp một biến thể an toàn / thoát trong Ruby 1.8. Trong Ruby 1.9 IO::popencũng chấp nhận một mảng.

Đơn giản chỉ cần chuyển mọi tùy chọn và đối số dưới dạng một mảng cho một trong những cuộc gọi này.

Nếu bạn không chỉ cần trạng thái thoát mà còn là kết quả bạn có thể muốn sử dụng 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

Lưu ý rằng biểu mẫu khối sẽ tự động đóng stdin, stdout và stderr - nếu không chúng phải được đóng lại rõ ràng .

Thêm thông tin ở đây: Hình thành các lệnh shell vệ sinh hoặc các cuộc gọi hệ thống trong Ruby


26
Đây là câu trả lời duy nhất thực sự trả lời câu hỏi và giải quyết vấn đề mà không đưa ra câu hỏi mới (đầu vào không được xác nhận).
Dogweather

2
Cảm ơn! Đây là loại câu trả lời tôi đã hy vọng. Một điều chỉnh: các getscuộc gọi sẽ vượt qua đối số nil, vì nếu không chúng ta chỉ nhận được dòng đầu tiên của đầu ra. Vì vậy, ví dụ stdout.gets(nil).
Greg Giá

3
stdin, stdout và stderr nên được đóng rõ ràng ở dạng không chặn .
Yarin

Có ai biết nếu có gì đó thay đổi trong Ruby 2.0 hoặc 2.1 không? Chỉnh sửa hoặc nhận xét sẽ được đánh giá cao ;-)
Simon Hürlimann

1
Tôi nghĩ rằng các cuộc thảo luận xung quanh Open3.popen3đang thiếu một vấn đề lớn: Nếu bạn có một quy trình con ghi nhiều dữ liệu vào thiết bị xuất chuẩn hơn một đường ống có thể giữ, quy trình con bị đình chỉ stderr.writevà chương trình của bạn bị kẹt stdout.gets(nil).
hagello

165

Chỉ dành cho bản ghi, nếu bạn muốn cả hai (kết quả đầu ra và hoạt động), bạn có thể làm:

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

4
Điều này thật đúng với gì mà tôi đã tìm kiếm. Cảm ơn bạn.
jdl

12
Điều đó chỉ thu được thiết bị xuất chuẩn, và thiết bị xuất chuẩn đi đến bàn điều khiển. Để lấy stderr, hãy sử dụng: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept

8
Câu trả lời này không an toàn và không nên được sử dụng - nếu lệnh là bất cứ thứ gì ngoài hằng số, thì cú pháp backtick có thể gây ra lỗi, có thể là lỗ hổng bảo mật. (Và ngay cả khi đó là một hằng số, nó có thể sẽ khiến ai đó sử dụng nó cho một hằng số sau đó và gây ra lỗi.) Xem câu trả lời của Simon Hürlimann để có giải pháp chính xác.
Greg Giá

23
kudos cho Greg Price để hiểu về nhu cầu thoát khỏi đầu vào của người dùng, nhưng không đúng khi nói câu trả lời này vì văn bản là không an toàn. Phương thức Open3 được đề cập phức tạp hơn và đưa ra nhiều phụ thuộc hơn, và lập luận rằng ai đó sẽ "sử dụng nó cho một hằng số sau này" là một người rơm. Đúng, bạn có thể sẽ không sử dụng chúng trong ứng dụng Rails, nhưng đối với tập lệnh tiện ích hệ thống đơn giản không có khả năng nhập liệu không đáng tin cậy của người dùng, backticks hoàn toàn ổn và không ai cảm thấy tồi tệ khi sử dụng chúng.
sbeam

69

Cách đơn giản để làm điều này một cách chính xác và an toàn là sử dụng Open3.capture2(), Open3.capture2e()hoặc Open3.capture3().

Sử dụng backticks của ruby ​​và %xbí danh của nó KHÔNG ĐẢM BẢO THEO BẤT CỨ ĐIỀU KIỆN NÀO nếu được sử dụng với dữ liệu không đáng tin cậy. Đó là NGUY HIỂM , đơn giản và đơn giản:

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

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

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

Các systemchức năng, ngược lại, thoát lý lẽ đúng nếu được sử dụng một cách chính xác :

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

Rắc rối là, nó trả về mã thoát thay vì đầu ra và việc bắt mã sau bị sai lệch và lộn xộn.

Câu trả lời tốt nhất trong chủ đề này cho đến nay đề cập đến Open3, nhưng không phải là các chức năng phù hợp nhất cho nhiệm vụ. Open3.capture2, capture2ecapture3làm việc như system, nhưng lợi nhuận hai hoặc ba đối số:

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

Một đề cập khác IO.popen(). Cú pháp có thể vụng về theo nghĩa là nó muốn một mảng làm đầu vào, nhưng nó cũng hoạt động:

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

Để thuận tiện, bạn có thể gói Open3.capture3()trong một hàm, ví dụ:

#
# 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

Thí dụ:

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

Mang lại kết quả như sau:

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
Đây là câu trả lời chính xác. Nó cũng là thông tin nhất. Điều duy nhất còn thiếu là một cảnh báo về việc đóng std * s. Xem bình luận khác này : require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } Lưu ý rằng biểu mẫu khối sẽ tự động đóng stdin, stdout và stderr- nếu không chúng phải được đóng lại rõ ràng .
Peter H. Boling

@ PeterH.Boling: Tốt nhất tôi biết capture2, capture2ecapture3cũng tự động đóng chúng std * s. (Ít nhất, tôi không bao giờ gặp phải vấn đề về vấn đề của mình.)
Denis de Bernardy

không sử dụng hình thức khối, không có cách nào để một codebase biết khi nào nên đóng một cái gì đó, vì vậy tôi rất nghi ngờ chúng đang bị đóng. Bạn có thể không bao giờ gặp phải sự cố vì không đóng chúng sẽ không gây ra sự cố trong một quy trình ngắn và nếu bạn khởi động lại một quy trình dài thường xuyên, otto sẽ không xuất hiện ở đó trừ khi bạn mở std * s trong một vòng lặp. Linux có giới hạn mô tả tệp cao, bạn có thể đạt được, nhưng cho đến khi bạn nhấn nó, bạn sẽ không thấy "lỗi".
Peter H. Boling

2
@ PeterH.Boling: Không không, xem mã nguồn. Các chức năng chỉ là hàm bao quanh Open3#popen2, popen2epopen3với một khối được xác định trước: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
Denis de Bernardy

1
@Dennis de Barnardy Có lẽ bạn bị mất rằng tôi liên kết với các tài liệu cùng lớp (mặc dù cho Ruby 2.0.0, và một phương pháp khác nhau. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/... Từ ví dụ : `` `stdin, stdout, stderr, Wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = Wait_thr [: pid] # pid của quá trình bắt đầu ... stdin.close # stdin , stdout và stderr nên được đóng lại một cách rõ ràng trong hình thức này. stdout.close stderr.close `` `Tôi chỉ trích dẫn tài liệu." # stdin, stdout và stderr nên được đóng rõ ràng trong mẫu này. "
Peter H. Boling

61

Bạn có thể sử dụng hệ thống () hoặc% x [] tùy thuộc vào loại kết quả bạn cần.

system () trả về true nếu lệnh được tìm thấy và chạy thành công, sai thì ngược lại.

>> 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

Mặt khác,% x [..] lưu kết quả của lệnh dưới dạng chuỗi:

>> 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 bài viết blog của Jay Fields giải thích chi tiết sự khác biệt giữa việc sử dụng hệ thống, exec và% x [..].


2
Cảm ơn mẹo sử dụng% x []. Nó chỉ giải quyết được một vấn đề mà tôi gặp phải khi sử dụng tick ngược trong tập lệnh ruby ​​trong Mac OS X. Khi chạy cùng một tập lệnh trên máy Windows với Cygwin, nó đã thất bại vì đánh dấu ngược, nhưng hoạt động với% x [].
Henrik Warne

22

Nếu bạn cần thoát khỏi các đối số, trong Ruby 1.9 IO.popen cũng chấp nhận một mảng:

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

Trong các phiên bản trước, bạn có thể sử dụng Open3.popen3 :

require "open3"

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

Nếu bạn cũng cần vượt qua stdin, điều này sẽ hoạt động trong cả 1.9 và 1.8:

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

Cảm ơn! Đây là hoàn hảo.
Greg Giá

21

Bạn sử dụng backticks:

`ls`

5
Backticks không tạo ra đầu ra tại thiết bị đầu cuối.
Mei

3
Nó không tạo ra stderr nhưng nó cho ra thiết bị xuất chuẩn.
Nickolay Kondratenko

1
Nó không ghi vào thiết bị xuất chuẩn hoặc thiết bị xuất chuẩn. Hãy thử ví dụ này ruby -e '%x{ls}'- lưu ý, không có đầu ra. (fyi %x{}tương đương với backticks.)
ocodo

Điều này đã làm việc tuyệt vời. Việc sử dụng shsẽ lặp lại đầu ra cho bàn điều khiển (tức là STDOUT) cũng như trả lại nó. Điều này không.
Joshua Pinter

19

Một cách khác là:

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

Lưu ý rằng ký tự "ống" trước khi "ls" mở. Điều này cũng có thể được sử dụng để cung cấp dữ liệu vào đầu vào tiêu chuẩn của chương trình cũng như đọc đầu ra tiêu chuẩn của nó.


Chỉ sử dụng điều này để đọc đầu ra tiêu chuẩn từ lệnh aws cli để đọc json và không phải giá trị trả về chính thức của 'true'
kraftydevil

14

Tôi thấy rằng những điều sau đây là hữu ích nếu bạn cần giá trị trả về:

result = %x[ls]
puts result

Tôi đặc biệt muốn liệt kê các pids của tất cả các quy trình Java trên máy của tôi và đã sử dụng điều này:

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

Đó là một giải pháp tuyệt vời.
Ronan Louarn


9

Mặc dù sử dụng backticks hoặc popen thường là những gì bạn thực sự muốn, nhưng nó không thực sự trả lời câu hỏi được hỏi. Có thể có lý do hợp lệ để nắm bắt systemđầu ra (có thể để kiểm tra tự động). Một chút Googling đã đưa ra một câu trả lời tôi nghĩ rằng tôi sẽ đăng ở đây vì lợi ích của người khác.

Vì tôi cần điều này để kiểm tra, ví dụ của tôi sử dụng thiết lập khối để nắm bắt đầu ra tiêu chuẩn do systemcuộc gọi thực tế được chôn trong mã đang được kiểm tra:

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

Phương pháp này nắm bắt bất kỳ đầu ra nào trong khối đã cho bằng cách sử dụng tempfile để lưu trữ dữ liệu thực tế. Ví dụ sử dụng:

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

Bạn có thể thay thế systemcuộc gọi bằng bất cứ điều gì mà cuộc gọi nội bộ system. Bạn cũng có thể sử dụng một phương pháp tương tự để chụp stderrnếu bạn muốn.


8

Nếu bạn muốn đầu ra được chuyển hướng đến một tệp bằng cách sử dụng Kernel#system, bạn có thể sửa đổi các mô tả như thế này:

chuyển hướng stdout và stderr sang một tệp (/ tmp / log) trong chế độ chắp thêm:

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

Đối với một lệnh chạy dài, điều này sẽ lưu trữ đầu ra trong thời gian thực. Bạn cũng có thể, lưu trữ đầu ra bằng IO.pipe và chuyển hướng nó từ hệ thống Kernel #.




Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.