Mendapatkan keluaran dari panggilan sistem () di Ruby

309

Jika saya memanggil perintah menggunakan sistem Kernel # di Ruby, bagaimana saya mendapatkan hasilnya?

system("ls")
Macha
sumber
1
Anda mungkin ingin melihat utas ini di comp.lang.ruby
Manrico Corazzi
Ini adalah ulir tangan yang sangat, terima kasih. Kelas untuk menjalankan perintah dan mendapatkan umpan balik sangat bagus dalam kode sampel.
ylluminate
3
Untuk googler masa depan. Jika Anda ingin mempelajari tentang panggilan perintah sistem lain dan perbedaannya, lihat jawaban SO ini .
Uzbekjon

Jawaban:

347

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
Craig Walker
sumber
4
bagaimana jika saya perlu memberikan variabel sebagai bagian dari perintah saya? Artinya, apa yang akan diterjemahkan oleh sistem ("ls" + nama file) ketika backtick digunakan?
Vijay Dev
47
Anda dapat melakukan evaluasi ekspresi seperti yang Anda lakukan dengan string biasa: ls #{filename}.
Craig Walker
36
Jawaban ini tidak disarankan: ini memperkenalkan masalah baru dari input pengguna yang tidak bersih.
Dogweather
4
@Dogweather: itu mungkin benar, tetapi apakah ini berbeda dari metode lainnya?
Craig Walker
20
jika Anda ingin menangkap stderr, tuliskan 2> & 1 di akhir perintah Anda. misalnya output =command 2>&1
micred
243

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 systemdan Open3.popen3berikan varian aman / melarikan diri di Ruby 1.8. Di Ruby 1.9 IO::popenjuga 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

Simon Hürlimann
sumber
26
Ini adalah satu-satunya jawaban yang benar-benar menjawab pertanyaan dan menyelesaikan masalah tanpa memperkenalkan yang baru (input tidak bersih).
Dogweather
2
Terima kasih! Ini adalah jawaban yang saya harapkan. Satu koreksi: getspanggilan harus melewati argumen nil, karena kalau tidak kita hanya mendapatkan baris pertama dari output. Jadi misalnya stdout.gets(nil).
Greg Price
3
stdin, stdout dan stderr harus ditutup secara eksplisit dalam bentuk non-blok .
Yarin
Adakah yang tahu kalau ada yang berubah di Ruby 2.0 atau 2.1? Suntingan atau komentar akan dihargai ;-)
Simon Hürlimann
1
Saya pikir diskusi di sekitar Open3.popen3tidak 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).
hagello
165

Sekadar catatan, jika Anda menginginkan keduanya (hasil dan operasi) Anda dapat melakukannya:

output=`ls no_existing_file` ;  result=$?.success?
FernandoFabreti
sumber
4
Ini persis apa yang saya cari. Terima kasih.
jdl
12
Itu hanya menangkap stdout, dan stderr pergi ke konsol. Untuk mendapatkan stderr, gunakan: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept
8
Jawaban ini tidak aman dan tidak boleh digunakan - jika perintahnya adalah konstan, maka sintaks backtick kemungkinan menyebabkan bug, kemungkinan kerentanan keamanan. (Dan bahkan jika itu adalah konstanta, itu mungkin akan menyebabkan seseorang menggunakannya untuk yang tidak konstan di kemudian hari dan menyebabkan bug.) Lihat jawaban Simon Hürlimann untuk solusi yang benar.
Greg Price
23
kudos to Greg Price untuk memahami tentang perlunya melarikan diri input pengguna, tetapi tidak benar untuk mengatakan jawaban ini karena ditulis tidak aman. Metode Open3 yang disebutkan lebih rumit dan memperkenalkan lebih banyak dependensi, dan argumen bahwa seseorang akan "menggunakannya untuk yang tidak konstan nanti" adalah sebuah kesalahan besar. Benar, Anda mungkin tidak akan menggunakannya dalam aplikasi Rails, tetapi untuk skrip utilitas sistem sederhana tanpa kemungkinan input pengguna yang tidak dipercaya, backtick baik-baik saja dan tidak ada yang harus merasa tidak enak menggunakannya.
sbeam
69

Cara mudah untuk melakukan ini dengan benar dan aman adalah dengan menggunakan Open3.capture2(), Open3.capture2e()atau Open3.capture3().

Menggunakan backtick ruby ​​dan %xaliasnya 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 systemfungsi, 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, capture2edan capture3bekerja 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')
Denis de Bernardy
sumber
2
Ini jawaban yang benar. Ini juga yang paling informatif. Satu-satunya hal yang hilang adalah peringatan tentang menutup std * s. Lihat komentar lain ini : 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 .
Peter H. Boling
@ PeterH.Boling: Terbaik Saya sadar, itu capture2, capture2edan capture3juga menutup mereka std * s secara otomatis. (Paling tidak, saya tidak pernah mengalami masalah pada akhirnya.)
Denis de Bernardy
tanpa menggunakan formulir blok tidak ada cara bagi basis kode untuk mengetahui kapan sesuatu harus ditutup, jadi saya sangat ragu mereka sedang ditutup. Anda mungkin tidak pernah mengalami masalah karena tidak menutupnya tidak akan menyebabkan masalah dalam proses yang singkat, dan jika Anda cukup sering memulai proses yang berjalan lama, otto tidak akan muncul di sana kecuali jika Anda membuka std * s di satu lingkaran. Linux memiliki batas deskriptor file yang tinggi, yang dapat Anda tekan, tetapi sampai Anda menekannya Anda tidak akan melihat "bug".
Peter H. Boling
2
@ PeterH.Boling: Tidak, tidak, lihat kode sumber. Fungsinya hanya pembungkus saja Open3#popen2, popen2edan popen3dengan blok yang telah ditentukan: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/…
Denis de Bernardy
1
@Dennis de Barnardy Mungkin Anda melewatkan bahwa saya terhubung ke dokumentasi kelas yang sama (walaupun untuk Ruby 2.0.0, dan metode yang berbeda. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… Dari contoh : `` `stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = wait_thr [: pid] # pid dari proses yang dimulai ... stdin.close # stdin , stdout dan stderr harus ditutup secara eksplisit dalam formulir ini. stdout.close stderr.close `` `Saya baru saja mengutip dokumentasi." # stdin, stdout dan stderr harus ditutup secara eksplisit dalam formulir ini. "
Peter H. Boling
61

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 [..].

Martin Gross
sumber
2
Terima kasih atas tip penggunaan% x []. Itu hanya memecahkan masalah yang saya miliki di mana saya menggunakan kutu kembali dalam skrip ruby ​​di Mac OS X. Saat menjalankan skrip yang sama pada mesin Windows dengan Cygwin, gagal karena kutu belakang, tetapi bekerja dengan% x [].
Henrik Warne
22

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"
Lri
sumber
Terima kasih! Ini sempurna.
Greg Price
21

Anda menggunakan backticks:

`ls`
kekacauan
sumber
5
Backticks tidak menghasilkan output di terminal.
Mei
3
Itu tidak menghasilkan stderr tetapi memberikan stdout.
Nickolay Kondratenko
1
Itu tidak menulis ke stdout atau stderr. Mari kita coba contoh ini ruby -e '%x{ls}'- perhatikan, tidak ada output. (fyi %x{}setara dengan backticks.)
ocodo
Ini bekerja dengan baik. Menggunakan shakan menggemakan output ke konsol (yaitu STDOUT) serta mengembalikannya. Ini tidak.
Joshua Pinter
19

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.

dwc
sumber
Hanya menggunakan ini untuk membaca output standar dari perintah aws cli untuk membaca json dan bukan nilai pengembalian resmi 'benar'
kraftydevil
14

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]
Geoff van der Meer
sumber
Ini solusi hebat.
Ronan Louarn
13

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 .

Yarin
sumber
9

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 systemkeluaran (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 systempanggilan 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 systempanggilan dengan apa pun yang panggilan internal system. Anda juga bisa menggunakan metode serupa untuk menangkap stderrjika Anda mau.

Eric Anderson
sumber
8

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

Ashrith
sumber
0

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

dac2009
sumber
-1
puts `date`
puts $?


Mon Mar  7 19:01:15 PST 2016
pid 13093 exit 0
Ron Hinchley
sumber