Jika saya memanggil perintah menggunakan sistem Kernel # di Ruby, bagaimana saya mendapatkan hasilnya?
system("ls")
Jika saya memanggil perintah menggunakan sistem Kernel # di Ruby, bagaimana saya mendapatkan hasilnya?
system("ls")
Jawaban:
Saya ingin sedikit memperluas & memperjelas jawaban kekacauan .
Jika Anda mengelilingi perintah Anda dengan backticks, maka Anda tidak perlu (secara eksplisit) memanggil sistem () sama sekali. Backtick menjalankan perintah dan mengembalikan output sebagai string. Anda kemudian dapat menetapkan nilai ke variabel seperti:
output = `ls`
p output
atau
printf output # escapes newline chars
ls #{filename}
.
command 2>&1
Ketahuilah bahwa semua solusi tempat Anda meneruskan string berisi nilai yang diberikan pengguna system
, %x[]
dll. Tidak aman! Tidak aman sebenarnya berarti: pengguna dapat memicu kode untuk berjalan dalam konteks dan dengan semua izin program.
Sejauh yang saya bisa katakan system
dan Open3.popen3
berikan varian aman / melarikan diri di Ruby 1.8. Di Ruby 1.9 IO::popen
juga menerima array.
Cukup berikan setiap opsi dan argumen sebagai array ke salah satu panggilan ini.
Jika Anda tidak hanya perlu status keluar tetapi juga hasil yang mungkin ingin Anda gunakan 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
Perhatikan bahwa form blok akan secara otomatis menutup stdin, stdout dan stderr- jika tidak mereka harus ditutup secara eksplisit .
Informasi lebih lanjut di sini: Membentuk perintah sanitary shell atau panggilan sistem di Ruby
gets
panggilan harus melewati argumen nil
, karena kalau tidak kita hanya mendapatkan baris pertama dari output. Jadi misalnya stdout.gets(nil)
.
Open3.popen3
tidak ada masalah besar: Jika Anda memiliki subproses yang menulis lebih banyak data ke stdout daripada yang bisa ditampung oleh pipa, subproses ditunda stderr.write
, dan program Anda macet stdout.gets(nil)
.
Sekadar catatan, jika Anda menginginkan keduanya (hasil dan operasi) Anda dapat melakukannya:
output=`ls no_existing_file` ; result=$?.success?
output=`ls no_existing_file 2>&1`; result=$?.success?
Cara mudah untuk melakukan ini dengan benar dan aman adalah dengan menggunakan Open3.capture2()
, Open3.capture2e()
atau Open3.capture3()
.
Menggunakan backtick ruby dan %x
aliasnya TIDAK AMAN DI BAWAH KEADAAN APAPUN jika digunakan dengan data yang tidak terpercaya. Ini BERBAHAYA , polos dan sederhana:
untrusted = "; date; echo"
out = `echo #{untrusted}` # BAD
untrusted = '"; date; echo"'
out = `echo "#{untrusted}"` # BAD
untrusted = "'; date; echo'"
out = `echo '#{untrusted}'` # BAD
The system
fungsi, sebaliknya, lolos argumen benar jika digunakan dengan benar :
ret = system "echo #{untrusted}" # BAD
ret = system 'echo', untrusted # good
Masalahnya adalah, ia mengembalikan kode keluar daripada output, dan menangkap yang terakhir berbelit-belit dan berantakan.
Jawaban terbaik di utas ini sejauh ini menyebutkan Open3, tetapi bukan fungsi yang paling cocok untuk tugas tersebut. Open3.capture2
, capture2e
dan capture3
bekerja seperti system
, tetapi mengembalikan dua atau tiga argumen:
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
Menyebutkan lain IO.popen()
. Sintaksnya bisa canggung dalam arti bahwa ia menginginkan sebuah array sebagai input, tetapi ia juga berfungsi:
out = IO.popen(['echo', untrusted]).read # good
Untuk kenyamanan, Anda dapat membungkus Open3.capture3()
suatu fungsi, misalnya:
#
# 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
Contoh:
p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')
Menghasilkan sebagai berikut:
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 }
Perhatikan bahwa formulir blok akan secara otomatis menutup stdin, stdout dan stderr- jika tidak mereka harus ditutup secara eksplisit .
capture2
, capture2e
dan capture3
juga menutup mereka std * s secara otomatis. (Paling tidak, saya tidak pernah mengalami masalah pada akhirnya.)
Open3#popen2
, popen2e
dan popen3
dengan blok yang telah ditentukan: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/…
Anda dapat menggunakan sistem () atau% x [] tergantung jenis hasil yang Anda butuhkan.
system () mengembalikan true jika perintah ditemukan dan dijalankan dengan sukses, false sebaliknya.
>> 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 [..] di sisi lain menyimpan hasil perintah sebagai string:
>> 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 blog posting oleh Jay Fields menjelaskan secara rinci perbedaan antara menggunakan sistem, exec dan% x [..].
Jika Anda perlu menghindari argumen, di Ruby 1.9 IO.popen juga menerima larik:
p IO.popen(["echo", "it's escaped"]).read
Di versi sebelumnya, Anda dapat menggunakan Open3.popen3 :
require "open3"
Open3.popen3("echo", "it's escaped") { |i, o| p o.read }
Jika Anda juga harus lulus stdin, ini harus bekerja di 1.9 dan 1.8:
out = IO.popen("xxd -p", "r+") { |io|
io.print "xyz"
io.close_write
io.read.chomp
}
p out # "78797a"
Anda menggunakan backticks:
`ls`
ruby -e '%x{ls}'
- perhatikan, tidak ada output. (fyi %x{}
setara dengan backticks.)
sh
akan menggemakan output ke konsol (yaitu STDOUT) serta mengembalikannya. Ini tidak.
Cara lain adalah:
f = open("|ls")
foo = f.read()
Perhatikan bahwa karakter "pipa" sebelum "ls" di buka. Ini juga dapat digunakan untuk memasukkan data ke dalam input standar program serta membaca output standarnya.
Saya menemukan bahwa yang berikut berguna jika Anda membutuhkan nilai kembali:
result = %x[ls]
puts result
Saya secara khusus ingin membuat daftar semua proses Java pada mesin saya, dan menggunakan ini:
ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]
Seperti yang dijelaskan Simon Hürlimann , Open3 lebih aman daripada backticks dll.
require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
Perhatikan bahwa form blok akan secara otomatis menutup stdin, stdout dan stderr- jika tidak mereka harus ditutup secara eksplisit .
Meskipun menggunakan backticks atau popen sering kali adalah yang Anda inginkan, itu sebenarnya tidak menjawab pertanyaan yang diajukan. Mungkin ada alasan yang sah untuk menangkap system
keluaran (mungkin untuk pengujian otomatis). Googling kecil muncul jawaban yang saya pikir saya akan posting di sini untuk kepentingan orang lain.
Karena saya memerlukan ini untuk menguji contoh saya menggunakan pengaturan blok untuk menangkap output standar karena system
panggilan sebenarnya dikubur dalam kode yang sedang diuji:
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
Metode ini menangkap setiap output di blok yang diberikan menggunakan tempfile untuk menyimpan data aktual. Contoh penggunaan:
captured_content = capture_stdout do
system 'echo foo'
end
puts captured_content
Anda dapat mengganti system
panggilan dengan apa pun yang panggilan internal system
. Anda juga bisa menggunakan metode serupa untuk menangkap stderr
jika Anda mau.
Jika Anda ingin output diarahkan ke file menggunakan Kernel#system
, Anda dapat memodifikasi deskriptor seperti ini:
redirect stdout dan stderr ke file (/ tmp / log) dalam mode tambahkan:
system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])
Untuk perintah yang berjalan lama, ini akan menyimpan output secara real time. Anda juga dapat, menyimpan output menggunakan IO.pipe dan mengalihkannya dari sistem Kernel #.
Sebagai pengganti sistem langsung (...), Anda dapat menggunakan Open3.popen3 (...)
Diskusi lebih lanjut: http://tech.natemurray.com/2007/03/ruby-shell-commands.html
Saya tidak menemukan ini di sini sehingga menambahkannya, saya punya beberapa masalah untuk mendapatkan hasil lengkap.
Anda dapat mengarahkan STDERR ke STDOUT jika Anda ingin menangkap STDERR menggunakan backtick.
output = `grep hosts / private / etc / * 2> & 1`
sumber: 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