Cara memanggil perintah shell dari Ruby

1077

Bagaimana cara saya memanggil perintah shell dari dalam program Ruby? Bagaimana cara mendapatkan output dari perintah ini kembali ke Ruby?

CodingWithoutComments
sumber
3
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
  1. 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

    value = `echo 'hi'`
    value = `#{cmd}`
  2. Sintaks bawaan, %x( cmd )

    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.

    Documents: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings

    value = %x( echo 'hi' )
    value = %x[ #{cmd} ]
  3. Kernel#system

    Menjalankan perintah yang diberikan dalam subkulit.

    Kembali truejika perintah ditemukan dan dijalankan dengan sukses, falsejika tidak.

    Documents: http://ruby-doc.org/core/Kernel.html#method-i-system

    wasGood = system( "echo 'hi'" )
    wasGood = system( cmd )
  4. 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

    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:

$?.exitstatus

Untuk bacaan lebih lanjut, lihat:

Steve Willard
sumber
4
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?
Omer Aslam
5
Dan IO # popen () dan Open3 # popen3 (). mentalized.net/journal/2010/03/08/...
hughdbrown
6
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?
skagedal
275

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

masukkan deskripsi gambar di sini

Ian
sumber
24
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 file
end
sinis
sumber
4
Apakah %x[ cmd ]mengembalikan array kepada Anda?
x-yuri
2
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 :)
Ian Ellis
65

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.

Mihai A
sumber
2
Posting yang dijelaskan di sini tidak membahas %xopsi sintaks.
Mei
+1 untuk Open4. Saya sudah mulai mencoba mengimplementasikan versi saya sendiri dari spawnmetodenya ketika saya menemukan ini.
Brandan
40

Favorit saya adalah Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
anshul
sumber
2
Saya juga suka open3, terutama Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
severin
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:

  1. Apakah Anda hanya ingin stdout atau Anda perlu stderr juga? Atau bahkan berpisah?
  2. Seberapa besar output Anda? Apakah Anda ingin menyimpan seluruh hasil dalam memori?
  3. Apakah Anda ingin membaca beberapa output Anda saat subproses masih berjalan?
  4. Apakah Anda memerlukan kode hasil?
  5. 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.

Sayangnya, itu sangat tergantung .

Nick Brosnahan
sumber
25

Satu opsi lagi:

Ketika anda:

  • perlu stderr dan stdout
  • tidak bisa / tidak akan menggunakan Open3 / Open4 (mereka melempar pengecualian di NetBeans di Mac saya, tidak tahu mengapa)

Anda dapat menggunakan pengalihan shell:

puts %x[cat bogus.txt].inspect
  => ""

puts %x[cat bogus.txt 2>&1].inspect
  => "cat: bogus.txt: No such file or directory\n"

The 2>&1sintaks bekerja di Linux , Mac dan Windows sejak hari-hari awal MS-DOS.

jg-faustus
sumber
25

Saya jelas bukan ahli Ruby, tapi saya akan mencobanya:

$ irb 
system "echo Hi"
Hi
=> true

Anda juga harus dapat melakukan hal-hal seperti:

cmd = 'ls'
system(cmd)
Steve Willard
sumber
21

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:

exec 'echo "hello $HOSTNAME"'

systemdan $?:

system 'false' 
puts $?

Backticks (`):

today = `date`

IO#popen:

IO.popen("date") { |f| puts f.gets }

Open3#popen3 - stdlib:

require "open3"
stdin, stdout, stderr = Open3.popen3('dc') 

Open4#popen4 - permata:

require "open4" 
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]
Perkakas
sumber
15

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:

quick_output = system("ls -la")
quick_bash = system("bash -c 'ls -la'")

Untuk mengetes:

system("echo $SHELL")
system('bash -c "echo $SHELL"')

Atau jika Anda menjalankan file skrip yang ada seperti

script_output = system("./my_script.sh")

Ruby seharusnya menghormati shebang, tetapi Anda selalu bisa menggunakannya

system("bash ./my_script.sh")

untuk memastikan, meskipun mungkin ada sedikit overhead dari /bin/shberlari /bin/bash, Anda mungkin tidak akan menyadarinya.

dragon788
sumber
11

Anda juga dapat menggunakan operator backtick (`), mirip dengan Perl:

directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory

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.

Rufo Sanchez
sumber
10

Kita dapat mencapainya dengan berbagai cara.

Menggunakan Kernel#exec, tidak ada setelah perintah ini dieksekusi:

exec('ls ~')

Menggunakan backticks or %x

`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"

Menggunakan Kernel#systemperintah, kembali truejika berhasil, falsejika tidak berhasil dan kembali niljika eksekusi perintah gagal:

system('ls ~')
=> true
nkm
sumber
10

Cara termudah adalah, misalnya:

reboot = `init 6`
puts reboot
Alex Lorsung
sumber
9

Menggunakan jawaban di sini dan ditautkan dalam jawaban Mihai, saya mengumpulkan fungsi yang memenuhi persyaratan ini:

  1. Rapi menangkap STDOUT dan STDERR sehingga mereka tidak "bocor" ketika skrip saya dijalankan dari konsol.
  2. Mengizinkan argumen diteruskan ke shell sebagai array, jadi tidak perlu khawatir untuk melarikan diri.
  3. 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'

class ShellError < StandardError; end

#actual function:
def system_quietly(*cmd)
  exit_status=nil
  err=nil
  out=nil
  Open3.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.value
  end
  if exit_status.to_i > 0
    err = err.chomp if err
    raise ShellError, err
  elsif out
    return out.chomp
  else
    return true
  end
end

#calling it:
begin
  puts system_quietly('which', 'ruby')
rescue ShellError
  abort "Looks like you don't have the `ruby` command. Odd."
end

#output: => "/Users/me/.rvm/rubies/ruby-1.9.2-p136/bin/ruby"
Ryan Tate
sumber
9

Jangan lupa spawnperintah untuk membuat proses latar belakang untuk menjalankan perintah yang ditentukan. Anda bahkan bisa menunggu penyelesaiannya menggunakan Processkelas dan yang dikembalikan pid:

pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
Process.wait pid

pid = spawn(RbConfig.ruby, "-eputs'Hello, world!'")
Process.wait pid

Doc mengatakan: Metode ini mirip dengan #systemtetapi tidak menunggu perintah selesai.

Monsieurart
sumber
2
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.

The dokumentasi Ruby memiliki contoh yang cukup baik:

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 => true or 0 : 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.  See Process.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, ...) or not
    :close_others => false : inherit fds (default for system and exec)
    :close_others => true  : dont inherit (default for spawn and IO.popen)
Kashyap
sumber
6

Metode backticks (`) adalah yang paling mudah untuk memanggil perintah shell dari Ruby. Ini mengembalikan hasil dari perintah shell:

     url_request = 'http://google.com'
     result_of_shell_command = `curl #{url_request}`
ya
sumber
5

Diberi perintah seperti attrib:

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.

Lihat " Menjalankan perintah di ruby " dan dokumentasi Open3 Ruby .

barlop
sumber
3

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:

WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0)

atau

WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0)

Keduanya akan menyimpan ipconfigoutput di dalamnyalog.txt , tetapi tidak ada jendela yang muncul.

Kamu akan membutuhkan require 'win32ole' di dalam skrip Anda.

system(), exec()dan spawn()semua akan muncul jendela yang menjengkelkan itu ketika menggunakan TK dan rubyw.

lucaortis
sumber
-2

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 )
JayCrossler
sumber