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")
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")
Câu trả lời:
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
ls #{filename}
.
command 2>&1
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 system
và chỉ Open3.popen3
cung cấp một biến thể an toàn / thoát trong Ruby 1.8. Trong Ruby 1.9 IO::popen
cũ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
gets
cuộ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)
.
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.write
và chương trình của bạn bị kẹt stdout.gets(nil)
.
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?
output=`ls no_existing_file 2>&1`; result=$?.success?
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à %x
bí 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 system
chứ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
, capture2e
Và capture3
là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')
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 .
capture2
, capture2e
và capture3
cũ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.)
Open3#popen2
, popen2e
và popen3
với một khối được xác định trước: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
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 [..].
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"
Bạn sử dụng backticks:
`ls`
ruby -e '%x{ls}'
- lưu ý, không có đầu ra. (fyi %x{}
tương đương với backticks.)
sh
sẽ 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.
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ó.
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]
Như Simon Hürlimann đã giải thích , Open3 an toàn hơn backticks, v.v.
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 .
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 system
cuộ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ế system
cuộ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 stderr
nếu bạn muốn.
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 #.
Là một thay thế hệ thống trực tiếp (...), bạn có thể sử dụng Open3.popen3 (...)
Thảo luận thêm: http : // Tech.nHR bồ.com 2007/03 / ruby-shell-commands.html
Tôi đã không tìm thấy cái này ở đây vì vậy thêm nó, tôi có một số vấn đề nhận được đầu ra đầy đủ.
Bạn có thể chuyển hướng STDERR sang STDOUT nếu bạn muốn chụp STDERR bằng cách sử dụng backtick.
đầu ra = `grep host / private / etc / * 2> & 1`
nguồn: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
puts `date`
puts $?
Mon Mar 7 19:01:15 PST 2016
pid 13093 exit 0