Meskipun pertanyaan ini bermanfaat, itu tidak ditanyakan dengan baik. Ruby memiliki banyak cara untuk memanggil sub-shell yang didokumentasikan dengan baik dan mudah ditemukan dengan membaca dokumentasi Kernel dan Open3 dan mencari di sini di SO.
the Tin Man
1
Sayangnya topik ini cukup rumit. Open3( docs ) adalah pilihan terbaik untuk sebagian besar situasi, IMO, tetapi pada versi Ruby yang lebih lama, itu tidak akan menghormati yang diubah PATH( bugs.ruby-lang.org/issues/8004 ), dan tergantung pada bagaimana Anda memberikan argumen (khususnya , jika Anda menggunakan hash opts dengan non-kata kunci), itu dapat rusak. Tetapi, jika Anda menghadapi situasi itu, maka Anda melakukan sesuatu yang cukup maju dan Anda bisa mencari tahu apa yang harus dilakukan dengan membaca implementasi Open3.
Joshua Cheek
3
Saya terkejut tidak ada yang disebutkan Shellwords.escape( doc ). Anda tidak ingin memasukkan input pengguna secara langsung ke perintah shell - lupakan dulu! Lihat juga perintah injeksi .
Kelvin
Jawaban:
1319
Penjelasan ini didasarkan pada naskah Ruby yang dikomentari dari seorang teman saya. Jika Anda ingin meningkatkan skrip, silakan memperbaruinya di tautan.
Pertama, perhatikan bahwa ketika Ruby memanggil shell, itu biasanya memanggil /bin/sh, bukan Bash. Beberapa sintaks Bash tidak didukung oleh /bin/shsemua sistem.
Berikut ini cara untuk menjalankan skrip shell:
cmd ="echo 'hi'"# Sample string that can be used
Kernel#` , yang biasa disebut backticks - `cmd`
Ini seperti banyak bahasa lain, termasuk Bash, PHP, dan Perl.
Mengembalikan hasil (yaitu keluaran standar) dari perintah shell.
Mengikuti xkarakter adalah pembatas, yang bisa berupa karakter apa saja. Jika pembatas adalah salah satu karakter (, [, {, atau <, literal terdiri dari karakter sampai dengan pembatas penutupan cocok, dengan mempertimbangkan pasang pembatas bersarang. Untuk semua pembatas lainnya, literal terdiri dari karakter hingga kemunculan karakter pembatas berikutnya. Interpolasi string #{ ... }diizinkan.
Mengembalikan hasil (yaitu keluaran standar) dari perintah shell, sama seperti backticks.
exec("echo 'hi'")exec( cmd )# Note: this will never be reached because of the line above
Berikut beberapa saran tambahan:,
$?yang sama dengan $CHILD_STATUS, mengakses status dari perintah sistem terakhir yang dijalankan jika Anda menggunakan backticks, system()atau %x{}. Anda kemudian dapat mengakses exitstatusdan pidproperti:
Saya perlu mencatat output dari executable saya di server produksi tetapi tidak menemukan cara. Saya menggunakan #{cmd}put dan logger.info ( #{cmd}). Apakah ada cara untuk mencatat hasil produksi mereka?
Demi kelengkapan (seperti yang saya pikir pertama kali ini juga akan menjadi perintah Ruby): Rake memiliki sh yang tidak "Jalankan perintah sistem cmd. Jika beberapa argumen diberikan perintah tidak dijalankan dengan shell (semantik yang sama dengan Kernel :: exec dan Kernel :: system) ".
sschuberth
40
Backticks tidak menangkap STDERR secara default. Tambahkan `2> & 1` ke perintah jika Anda ingin menangkap
Andrei Botalov
14
Saya pikir jawaban ini akan sedikit ditingkatkan jika dikatakan bahwa backticks dan% x mengembalikan "output", bukan "hasil", dari perintah yang diberikan. Yang terakhir bisa salah untuk status keluar. Atau hanya aku?
Wow haha. Sangat berguna walaupun fakta ini harus ada adalah disayangkan
Josh Bodah
Sebagai catatan tambahan, saya menemukan metode spawn () yang ditemukan di banyak tempat berbeda (misalnya, Kerneldan Processpaling serbaguna. Ini kurang lebih sama dengan PTY.spawn(), tetapi lebih umum
Smar
160
Cara saya suka melakukan ini adalah dengan menggunakan %xliteral, yang membuatnya mudah (dan dapat dibaca!) Untuk menggunakan kutipan dalam sebuah perintah, seperti:
directorylist =%x[find .-name '*test.rb'| sort]
Yang, dalam hal ini, akan mengisi daftar file dengan semua file uji di bawah direktori saat ini, yang dapat Anda proses seperti yang diharapkan:
directorylist.each do|filename|
filename.chomp!# work with fileend
di atas tidak bekerja untuk saya. `` <main> ': metode yang tidak terdefinisi each' for :String (NoMethodError) bagaimana cara kerjanya untuk Anda? Saya menggunakan ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]Apakah Anda yakin array dikembalikan dari perintah sehingga loop akan benar-benar berfungsi?
Nasser
% x [cmd] .split ("\ n") akan mengembalikan daftar :)
Jika Anda hanya perlu mendapatkan output gunakan backticks.
Saya membutuhkan hal-hal yang lebih maju seperti STDOUT dan STDERR jadi saya menggunakan permata Open4. Anda memiliki semua metode yang dijelaskan di sana.
Apakah ada dokumentasi tentang cara melakukan pengujian Spec dan Unit dengan Open3, atau Open lainnya di Ruby std-lib? Sulit untuk menguji shell out pada tingkat pemahaman saya saat ini.
FilBot3
29
Beberapa hal yang perlu dipikirkan ketika memilih antara mekanisme ini adalah:
Apakah Anda hanya ingin stdout atau Anda perlu stderr juga? Atau bahkan berpisah?
Seberapa besar output Anda? Apakah Anda ingin menyimpan seluruh hasil dalam memori?
Apakah Anda ingin membaca beberapa output Anda saat subproses masih berjalan?
Apakah Anda memerlukan kode hasil?
Apakah Anda memerlukan objek Ruby yang mewakili proses dan memungkinkan Anda membunuhnya sesuai permintaan?
Anda mungkin perlu sesuatu dari tanda kutip mundur sederhana ( ``), system()dan IO.popenuntuk full-blown Kernel.fork/ Kernel.execdengan IO.pipedan IO.select.
Anda mungkin juga ingin membuang batas waktu ke dalam campuran jika sub-proses terlalu lama untuk dijalankan.
Jika Anda benar-benar membutuhkan Bash, per catatan dalam jawaban "terbaik".
Pertama, perhatikan bahwa ketika Ruby memanggil shell, itu biasanya memanggil /bin/sh, bukan Bash. Beberapa sintaks Bash tidak didukung oleh /bin/shsemua sistem.
Jika Anda perlu menggunakan Bash, masukkan ke bash -c "your Bash-only command"dalam metode panggilan yang Anda inginkan:
Menggunakan jawaban di sini dan ditautkan dalam jawaban Mihai, saya mengumpulkan fungsi yang memenuhi persyaratan ini:
Rapi menangkap STDOUT dan STDERR sehingga mereka tidak "bocor" ketika skrip saya dijalankan dari konsol.
Mengizinkan argumen diteruskan ke shell sebagai array, jadi tidak perlu khawatir untuk melarikan diri.
Menangkap status keluar dari perintah sehingga jelas ketika kesalahan telah terjadi.
Sebagai bonus, yang ini juga akan mengembalikan STDOUT dalam kasus di mana perintah shell berhasil keluar (0) dan meletakkan apa pun di STDOUT. Dengan cara ini, ini berbeda dari system, yang hanya mengembalikantrue dalam kasus seperti itu.
Kode berikut. Fungsi spesifiknya adalah system_quietly:
require'open3'classShellError<StandardError;end#actual function:def system_quietly(*cmd)
exit_status=nil
err=nilout=nilOpen3.popen3(*cmd)do|stdin, stdout, stderr, wait_thread|
err = stderr.gets(nil)out= stdout.gets(nil)[stdin, stdout, stderr].each{|stream| stream.send('close')}
exit_status = wait_thread.valueendif exit_status.to_i >0
err = err.chomp if err
raiseShellError, err
elsifoutreturnout.chomp
elsereturntrueendend#calling it:begin
puts system_quietly('which','ruby')rescueShellError
abort "Looks like you don't have the `ruby` command. Odd."end#output: => "/Users/me/.rvm/rubies/ruby-1.9.2-p136/bin/ruby"
Jangan lupa spawnperintah untuk membuat proses latar belakang untuk menjalankan perintah yang ditentukan. Anda bahkan bisa menunggu penyelesaiannya menggunakan Processkelas dan yang dikembalikan pid:
Kernel.spawn()tampaknya jauh lebih fleksibel daripada semua opsi lainnya.
Kashyap
6
Jika Anda memiliki kasing yang lebih kompleks dari kasing yang tidak dapat ditangani ``, periksa Kernel.spawn() . Ini tampaknya menjadi yang paling umum / berfitur lengkap yang disediakan oleh Ruby saham untuk menjalankan perintah eksternal.
Anda dapat menggunakannya untuk:
buat grup proses (Windows).
redirect masuk, keluar, kesalahan ke file / satu sama lain.
atur env vars, umask.
ubah direktori sebelum menjalankan perintah.
atur batas sumber daya untuk CPU / data / dll.
Lakukan semua yang dapat dilakukan dengan opsi lain di jawaban lain, tetapi dengan lebih banyak kode.
env: hash
name => val :set the environment variable
name =>nil: unset the environment variable
command...:
commandline : command line string which is passed to the standard shell
cmdname, arg1,...: command name and one or more arguments (no shell)[cmdname, argv0], arg1,...: command name, argv[0]and zero or more arguments (no shell)
options: hash
clearing environment variables::unsetenv_others =>true: clear environment variables except specified by env
:unsetenv_others =>false: dont clear (default)
process group::pgroup =>trueor0: make a new process group:pgroup => pgid :join to specified process group:pgroup =>nil: dont change the process group(default)
create new process group:Windows only
:new_pgroup =>true: the new process is the root process of a new process group:new_pgroup =>false: dont create a new process group(default)
resource limit: resourcename is core, cpu, data, etc.SeeProcess.setrlimit.:rlimit_resourcename => limit
:rlimit_resourcename =>[cur_limit, max_limit]
current directory::chdir => str
umask::umask =>int
redirection:
key:
FD : single file descriptor in child process
[FD, FD,...]: multiple file descriptor in child process
value:
FD : redirect to the file descriptor in parent process
string: redirect to file with open(string,"r"or"w")[string]: redirect to file with open(string,File::RDONLY)[string, open_mode]: redirect to file with open(string, open_mode,0644)[string, open_mode, perm]: redirect to file with open(string, open_mode, perm)[:child, FD]: redirect to the redirected file descriptor
:close : close the file descriptor in child process
FD is one of follows
:in: the file descriptor 0 which is the standard input
:out: the file descriptor 1 which is the standard output
:err : the file descriptor 2 which is the standard error
integer : the file descriptor of specified the integer
io : the file descriptor specified as io.fileno
file descriptor inheritance: close non-redirected non-standard fds (3,4,5,...)ornot:close_others =>false: inherit fds (defaultfor system andexec):close_others =>true: dont inherit (defaultfor spawn and IO.popen)
require'open3'
a="attrib"Open3.popen3(a)do|stdin, stdout, stderr|
puts stdout.read
end
Saya telah menemukan bahwa sementara metode ini tidak mudah diingat
system("thecommand")
atau
`thecommand`
di backticks, hal yang baik tentang metode ini dibandingkan dengan metode lain adalah backticks tampaknya tidak membiarkan saya putsperintah saya jalankan / simpan perintah saya ingin jalankan dalam variabel, dan system("thecommand")sepertinya tidak membiarkan saya mendapatkan output sedangkan metode ini memungkinkan saya melakukan kedua hal tersebut, dan memungkinkan saya mengakses stdin, stdout dan stderr secara mandiri.
Ini sebenarnya bukan jawaban tetapi mungkin seseorang akan merasakan manfaatnya:
Saat menggunakan TK GUI di Windows, dan Anda perlu memanggil perintah shell dari rubyw, Anda akan selalu memiliki jendela CMD yang mengganggu yang muncul kurang dari sedetik.
Inilah yang keren yang saya gunakan dalam skrip ruby pada OS X (sehingga saya dapat memulai skrip dan mendapatkan pembaruan bahkan setelah beralih dari jendela):
cmd =%Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
Open3
( docs ) adalah pilihan terbaik untuk sebagian besar situasi, IMO, tetapi pada versi Ruby yang lebih lama, itu tidak akan menghormati yang diubahPATH
( bugs.ruby-lang.org/issues/8004 ), dan tergantung pada bagaimana Anda memberikan argumen (khususnya , jika Anda menggunakan hash opts dengan non-kata kunci), itu dapat rusak. Tetapi, jika Anda menghadapi situasi itu, maka Anda melakukan sesuatu yang cukup maju dan Anda bisa mencari tahu apa yang harus dilakukan dengan membaca implementasiOpen3
.Shellwords.escape
( doc ). Anda tidak ingin memasukkan input pengguna secara langsung ke perintah shell - lupakan dulu! Lihat juga perintah injeksi .Jawaban:
Penjelasan ini didasarkan pada naskah Ruby yang dikomentari dari seorang teman saya. Jika Anda ingin meningkatkan skrip, silakan memperbaruinya di tautan.
Pertama, perhatikan bahwa ketika Ruby memanggil shell, itu biasanya memanggil
/bin/sh
, bukan Bash. Beberapa sintaks Bash tidak didukung oleh/bin/sh
semua sistem.Berikut ini cara untuk menjalankan skrip shell:
Kernel#`
, yang biasa disebut backticks -`cmd`
Ini seperti banyak bahasa lain, termasuk Bash, PHP, dan Perl.
Mengembalikan hasil (yaitu keluaran standar) dari perintah shell.
Documents: http://ruby-doc.org/core/Kernel.html#method-i-60
Sintaks bawaan,
%x( cmd )
Mengikuti
x
karakter adalah pembatas, yang bisa berupa karakter apa saja. Jika pembatas adalah salah satu karakter(
,[
,{
, atau<
, literal terdiri dari karakter sampai dengan pembatas penutupan cocok, dengan mempertimbangkan pasang pembatas bersarang. Untuk semua pembatas lainnya, literal terdiri dari karakter hingga kemunculan karakter pembatas berikutnya. Interpolasi string#{ ... }
diizinkan.Mengembalikan hasil (yaitu keluaran standar) dari perintah shell, sama seperti backticks.
Documents: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings
Kernel#system
Menjalankan perintah yang diberikan dalam subkulit.
Kembali
true
jika perintah ditemukan dan dijalankan dengan sukses,false
jika tidak.Documents: http://ruby-doc.org/core/Kernel.html#method-i-system
Kernel#exec
Mengganti proses saat ini dengan menjalankan perintah eksternal yang diberikan.
Tidak ada pengembalian, proses saat ini diganti dan tidak pernah dilanjutkan.
Documents: http://ruby-doc.org/core/Kernel.html#method-i-exec
Berikut beberapa saran tambahan:,
$?
yang sama dengan$CHILD_STATUS
, mengakses status dari perintah sistem terakhir yang dijalankan jika Anda menggunakan backticks,system()
atau%x{}
. Anda kemudian dapat mengaksesexitstatus
danpid
properti:Untuk bacaan lebih lanjut, lihat:
sumber
#{cmd}
put dan logger.info (#{cmd}
). Apakah ada cara untuk mencatat hasil produksi mereka?cmd
. Jika beberapa argumen diberikan perintah tidak dijalankan dengan shell (semantik yang sama dengan Kernel :: exec dan Kernel :: system) ".Berikut bagan alur berdasarkan pada " Kapan harus menggunakan setiap metode meluncurkan subproses di Ruby ". Lihat juga, " Menipu aplikasi agar berpikir stdout-nya adalah terminal, bukan pipa ".
sumber
Kernel
danProcess
paling serbaguna. Ini kurang lebih sama denganPTY.spawn()
, tetapi lebih umumCara saya suka melakukan ini adalah dengan menggunakan
%x
literal, yang membuatnya mudah (dan dapat dibaca!) Untuk menggunakan kutipan dalam sebuah perintah, seperti:Yang, dalam hal ini, akan mengisi daftar file dengan semua file uji di bawah direktori saat ini, yang dapat Anda proses seperti yang diharapkan:
sumber
%x[ cmd ]
mengembalikan array kepada Anda?each' for :String (NoMethodError)
bagaimana cara kerjanya untuk Anda? Saya menggunakanruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]
Apakah Anda yakin array dikembalikan dari perintah sehingga loop akan benar-benar berfungsi?Inilah artikel terbaik menurut saya tentang menjalankan skrip shell di Ruby: " 6 Cara Menjalankan Perintah Shell di Ruby ".
Jika Anda hanya perlu mendapatkan output gunakan backticks.
Saya membutuhkan hal-hal yang lebih maju seperti STDOUT dan STDERR jadi saya menggunakan permata Open4. Anda memiliki semua metode yang dijelaskan di sana.
sumber
%x
opsi sintaks.spawn
metodenya ketika saya menemukan ini.Favorit saya adalah Open3
sumber
stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
Beberapa hal yang perlu dipikirkan ketika memilih antara mekanisme ini adalah:
Anda mungkin perlu sesuatu dari tanda kutip mundur sederhana ( ``),
system()
danIO.popen
untuk full-blownKernel.fork
/Kernel.exec
denganIO.pipe
danIO.select
.Anda mungkin juga ingin membuang batas waktu ke dalam campuran jika sub-proses terlalu lama untuk dijalankan.
Sayangnya, itu sangat tergantung .
sumber
Satu opsi lagi:
Ketika anda:
Anda dapat menggunakan pengalihan shell:
The
2>&1
sintaks bekerja di Linux , Mac dan Windows sejak hari-hari awal MS-DOS.sumber
Saya jelas bukan ahli Ruby, tapi saya akan mencobanya:
Anda juga harus dapat melakukan hal-hal seperti:
sumber
Jawaban di atas sudah sangat bagus, tetapi saya benar-benar ingin berbagi artikel ringkasan berikut: " 6 Cara Menjalankan Perintah Shell di Ruby "
Pada dasarnya, ini memberitahu kita:
Kernel#exec
:system
dan$?
:Backticks (`):
IO#popen
:Open3#popen3
- stdlib:Open4#popen4
- permata:sumber
Jika Anda benar-benar membutuhkan Bash, per catatan dalam jawaban "terbaik".
Jika Anda perlu menggunakan Bash, masukkan ke
bash -c "your Bash-only command"
dalam metode panggilan yang Anda inginkan:Untuk mengetes:
Atau jika Anda menjalankan file skrip yang ada seperti
Ruby seharusnya menghormati shebang, tetapi Anda selalu bisa menggunakannya
untuk memastikan, meskipun mungkin ada sedikit overhead dari
/bin/sh
berlari/bin/bash
, Anda mungkin tidak akan menyadarinya.sumber
Anda juga dapat menggunakan operator backtick (`), mirip dengan Perl:
Berguna jika Anda membutuhkan sesuatu yang sederhana.
Metode mana yang ingin Anda gunakan tergantung pada apa yang ingin Anda capai; periksa dokumen untuk detail lebih lanjut tentang metode yang berbeda.
sumber
Kita dapat mencapainya dengan berbagai cara.
Menggunakan
Kernel#exec
, tidak ada setelah perintah ini dieksekusi:Menggunakan
backticks or %x
Menggunakan
Kernel#system
perintah, kembalitrue
jika berhasil,false
jika tidak berhasil dan kembalinil
jika eksekusi perintah gagal:sumber
Cara termudah adalah, misalnya:
sumber
Menggunakan jawaban di sini dan ditautkan dalam jawaban Mihai, saya mengumpulkan fungsi yang memenuhi persyaratan ini:
Sebagai bonus, yang ini juga akan mengembalikan STDOUT dalam kasus di mana perintah shell berhasil keluar (0) dan meletakkan apa pun di STDOUT. Dengan cara ini, ini berbeda dari
system
, yang hanya mengembalikantrue
dalam kasus seperti itu.Kode berikut. Fungsi spesifiknya adalah
system_quietly
:sumber
Jangan lupa
spawn
perintah untuk membuat proses latar belakang untuk menjalankan perintah yang ditentukan. Anda bahkan bisa menunggu penyelesaiannya menggunakanProcess
kelas dan yang dikembalikanpid
:Doc mengatakan: Metode ini mirip dengan
#system
tetapi tidak menunggu perintah selesai.sumber
Kernel.spawn()
tampaknya jauh lebih fleksibel daripada semua opsi lainnya.Jika Anda memiliki kasing yang lebih kompleks dari kasing yang tidak dapat ditangani
``
, periksaKernel.spawn()
. Ini tampaknya menjadi yang paling umum / berfitur lengkap yang disediakan oleh Ruby saham untuk menjalankan perintah eksternal.Anda dapat menggunakannya untuk:
The dokumentasi Ruby memiliki contoh yang cukup baik:
sumber
Metode backticks (`) adalah yang paling mudah untuk memanggil perintah shell dari Ruby. Ini mengembalikan hasil dari perintah shell:
sumber
Diberi perintah seperti
attrib
:Saya telah menemukan bahwa sementara metode ini tidak mudah diingat
atau
di backticks, hal yang baik tentang metode ini dibandingkan dengan metode lain adalah backticks tampaknya tidak membiarkan saya
puts
perintah saya jalankan / simpan perintah saya ingin jalankan dalam variabel, dansystem("thecommand")
sepertinya tidak membiarkan saya mendapatkan output sedangkan metode ini memungkinkan saya melakukan kedua hal tersebut, dan memungkinkan saya mengakses stdin, stdout dan stderr secara mandiri.Lihat " Menjalankan perintah di ruby " dan dokumentasi Open3 Ruby .
sumber
Ini sebenarnya bukan jawaban tetapi mungkin seseorang akan merasakan manfaatnya:
Saat menggunakan TK GUI di Windows, dan Anda perlu memanggil perintah shell dari rubyw, Anda akan selalu memiliki jendela CMD yang mengganggu yang muncul kurang dari sedetik.
Untuk menghindari ini, Anda dapat menggunakan:
atau
Keduanya akan menyimpan
ipconfig
output di dalamnyalog.txt
, tetapi tidak ada jendela yang muncul.Kamu akan membutuhkan
require 'win32ole'
di dalam skrip Anda.system()
,exec()
danspawn()
semua akan muncul jendela yang menjengkelkan itu ketika menggunakan TK dan rubyw.sumber
Inilah yang keren yang saya gunakan dalam skrip ruby pada OS X (sehingga saya dapat memulai skrip dan mendapatkan pembaruan bahkan setelah beralih dari jendela):
sumber