Ruby'den kabuk komutları nasıl çağrılır


1077

Ruby programının içinden kabuk komutlarını nasıl çağırırım? Daha sonra bu komutlardan çıktıyı Ruby'ye nasıl geri alabilirim?


3
Bu soru faydalı olsa da, iyi sorulmamıştır. Ruby, Kernel ve Open3 belgelerini okuyarak ve burada SO'da arama yaparak iyi belgelenmiş ve kolayca bulunabilen alt kabukları çağırmanın birçok yoluna sahiptir .
Tin Tin

1
Ne yazık ki bu konu oldukça karmaşık. Open3( dokümanlar ) çoğu durum için en iyi seçimdir, IMO, ancak Ruby'nin eski sürümlerinde, değiştirilmiş PATH( bugs.ruby-lang.org/issues/8004 ) ve argümanları nasıl geçirdiğinize bağlı olarak (özellikle , anahtar kelimeler olmayan opts hash kullanırsanız), kırılabilir. Ancak, bu durumlara çarparsanız, oldukça gelişmiş bir şey yapıyorsunuz ve uygulanmasını okuyarak ne yapacağınızı anlayabilirsiniz Open3.
Joshua Cheek

3
Kimsenin bahsetmediğine şaşırdım Shellwords.escape( doc ). Kullanıcı girişini doğrudan kabuk komutlarına eklemek istemezsiniz - önce kaçın! Ayrıca bkz . Komut enjeksiyonu .
Kelvin

Yanıtlar:


1319

Bu açıklama, bir arkadaşımın yorumladığı Ruby senaryosuna dayanmaktadır. Betiği geliştirmek istiyorsanız, bağlantıdan güncellemekten çekinmeyin.

İlk olarak, Ruby bir kabuğa seslendiğinde, genellikle Bash değil/bin/sh , çağırır . Bazı Bash sözdizimi tüm sistemlerde desteklenmez ./bin/sh

Kabuk betiği yürütmenin yolları:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` , yaygın olarak ters çentikler denir - `cmd`

    Bu, Bash, PHP ve Perl gibi diğer birçok dile benzer.

    Shell komutunun sonucunu (yani standart çıktı) döndürür.

    Dokümanlar: http://ruby-doc.org/core/Kernel.html#method-i-60

    value = `echo 'hi'`
    value = `#{cmd}`
    
  2. Dahili sözdizimi, %x( cmd )

    Aşağıdaki xkarakteri herhangi bir karakter olabilir Bir sınırlayıcı vardır. Sınırlayıcı karakterlerden biri ise (, [, {, veya <, edebi yuvalanmış sınırlayıcı çiftleri dikkate alarak, eşleştirme kapanış sınırlayıcı kadar karakterden oluşur. Diğer tüm sınırlayıcılar için değişmez değer, sınırlayıcı karakterin bir sonraki oluşumuna kadar olan karakterleri içerir. Dize enterpolasyonuna #{ ... }izin verilir.

    Tıpkı geri tıklamalar gibi kabuk komutunun sonucunu (yani standart çıktı) döndürür.

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

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

    Verilen komutu alt kabukta yürütür.

    trueKomutun bulunup başarıyla çalışıp çalışmadığını döndürür , falseaksi takdirde.

    Dokümanlar: http://ruby-doc.org/core/Kernel.html#method-i- system

    wasGood = system( "echo 'hi'" )
    wasGood = system( cmd )
    
  4. Kernel#exec

    Geçerli işlemi verilen harici komutu çalıştırarak değiştirir.

    Hiçbiri döndürmez, geçerli işlem değiştirilir ve hiçbir zaman devam etmez.

    Dokümanlar: 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
    

Aşağıda bazı ek öneriler verilmektedir: $?ki bu da $CHILD_STATUS, ters işaretleri kullanırsanız, son sistem yürütme komutunun durumuna erişir system()veya %x{}. Ardından exitstatusve pidözelliklerine erişebilirsiniz :

$?.exitstatus

Daha fazla okuma için bakınız:


4
Üretim sunucumda çalıştırılabilirimin çıktılarını günlüğe kaydetmem gerekiyor ama hiçbir şekilde bulunamadı. Ben koyar #{cmd}ve logger.info ( #{cmd}) kullandım. Çıktılarını üretime kaydetmenin bir yolu var mı?
Ömer Aslam

5
Ve IO # popen () ve Open3 # popen3 (). mentalized.net/journal/2010/03/08/…
hughdbrown

6
Bütünlük adına (Ben ilk düşündüğüm gibi bu da bir Ruby komut olacaktır): Rake sahiptir sh "Sistem komutunu çalıştırın gelmez cmdbirden argüman belirtilmemişse komut Kernel olarak (kabuğu ile aynı anlamsal çalıştırılmaz ::. exec ve Kernel :: system) ".
sschuberth

40
Backticks varsayılan olarak STDERR'ı yakalamaz. Yakalamak istiyorsanız komuta `2> &
1` ekleyin

14
Ben geri cevaplar ve% x verilen komutun "sonuç" yerine, "çıktı" döndürdüğünü söyleseydi bu cevap biraz daha iyi olacağını düşünüyorum. İkincisi çıkış durumu ile karıştırılabilir. Yoksa bu sadece ben miyim?
skagedal

275

24
Vay haha. Bunun olması gerektiği gerçeği talihsiz olmasına rağmen çok yararlı
Josh Bodah

Bir yan not olarak, birçok farklı yerde bulunan spawn () yöntemini buluyorum (örneğin Kernelve Processen çok yönlü olmak için. Aşağı yukarı aynı PTY.spawn(), ancak daha genel.
Smar

160

Bunu yapmak gibi yolu %x, gibi bir komutta tırnak kullanmayı kolaylaştırır (ve okunabilir!) Hazır bilgi kullanmaktır:

directorylist = %x[find . -name '*test.rb' | sort]

Bu durumda, beklendiği gibi işleyebileceğiniz dosya listesini geçerli dizinin altındaki tüm test dosyalarıyla doldurur:

directorylist.each do |filename|
  filename.chomp!
  # work with file
end

4
%x[ cmd ]Size bir dizi döndürüyor mu ?
x-yuri

2
yukarıdakiler benim için çalışmıyor. `` <main> ': tanımlanmamış yöntem each' for :String (NoMethodError) sizin için nasıl çalıştı ? Ben ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]döngü gerçekten çalışması için bir dizi komuttan döndüğünden emin misiniz?
Nasser

% x [cmd] .split ("\ n") yine de bir liste döndürecek :)
Ian Ellis

65

Ruby'de kabuk komut dosyalarını çalıştırma hakkında bence en iyi makale: "Ruby'de Shell Komutlarını Çalıştırmanın 6 Yolu ".

Sadece çıktıyı almanız gerekiyorsa geri iğne kullanın.

ST4OUT ve STDERR gibi daha gelişmiş şeylere ihtiyacım vardı, bu yüzden Open4 mücevher kullandım. Burada açıklanan tüm yöntemlere sahipsiniz.


2
Burada açıklanan yayın %xsözdizimi seçeneğini tartışmıyor .
Mei

Open4 için +1. Bunu bulduğumda zaten kendi spawnyönteminin sürümünü kullanmaya başlamıştım .
Brandan

40

Benim favorim Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }

2
Ayrıca open3'ü, özellikle Open3.capture3'ü de seviyorum: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
severin

Ruby std-lib'deki Open3 veya diğer Open'lar ile Spec ve Unit testlerinin nasıl yapılacağı hakkında herhangi bir belge var mı? Mevcut anlayış seviyemde kabukları test etmek zor.
16:15

29

Bu mekanizmalar arasında seçim yaparken düşünülmesi gereken bazı şeyler şunlardır:

  1. Sadece stdout mu istiyorsun yoksa stderr'a da mı ihtiyacın var? Yoksa ayrılmış mı?
  2. Çıktınız ne kadar büyük? Tüm sonucu hafızada tutmak istiyor musunuz?
  3. Alt işlem devam ederken çıktılarınızın bir kısmını okumak istiyor musunuz?
  4. Sonuç kodlarına mı ihtiyacınız var?
  5. Süreci temsil eden ve talep üzerine öldürmenize izin veren bir Ruby nesnesine ihtiyacınız var mı?

Basit ters vuruşlardan (``) system()ve IO.popentam şişirilmiş Kernel.fork/ Kernel.execile IO.pipeve arasında bir şeye ihtiyacınız olabilir IO.select.

Bir alt işlemin yürütülmesi çok uzun sürerse, karışıma zaman aşımlarını da atmak isteyebilirsiniz.

Ne yazık ki, bu çok bağlıdır .


25

Bir seçenek daha:

Sen ne zaman:

  • stderr kadar stderr'a ihtiyacım var
  • Open3 / Open4 kullanamıyorum / kullanmayacak (Mac'imde NetBeans'e istisnalar atıyorlar, neden bilmiyorlar)

Kabuk yeniden yönlendirmesini kullanabilirsiniz:

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

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

2>&1Sözdizimi arasında çalışır Linux , Mac ve Windows'ta MS-DOS ilk günlerinden beri.


25

Kesinlikle bir Ruby uzmanı değilim, ama bir şans vereceğim:

$ irb 
system "echo Hi"
Hi
=> true

Ayrıca aşağıdakileri de yapabilmeniz gerekir:

cmd = 'ls'
system(cmd)

21

Yukarıdaki cevaplar zaten harika, ama gerçekten aşağıdaki özet makaleyi paylaşmak istiyorum: " Ruby'de Shell Komutlarını Çalıştırmanın 6 Yolu "

Temel olarak, bize şunu söyler:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

systemve $?:

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 -- bir mücevher:

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

15

Gerçekten Bash gerekiyorsa, "en iyi" cevap notu başına.

İlk olarak, Ruby bir kabuğa seslendiğinde, genellikle Bash'ı değil/bin/sh , çağırır . Bazı Bash sözdizimi tüm sistemlerde desteklenmez ./bin/sh

Bash kullanmanız gerekiyorsa bash -c "your Bash-only command", istediğiniz arama yönteminin içine ekleyin :

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

Test etmek için:

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

Veya şu şekilde mevcut bir komut dosyasını çalıştırıyorsanız

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

Ruby shebang onur gerekir , ama her zaman kullanabilirsiniz

system("bash ./my_script.sh")

hafif bir havai hususlar olmasına rağmen, emin olmak için /bin/shçalışan /bin/bashmuhtemelen haber olmaz.


11

Perl'e benzeyen backtick operatörlerini (`) de kullanabilirsiniz:

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

Basit bir şeye ihtiyacınız varsa kullanışlı.

Hangi yöntemi kullanmak istediğiniz tam olarak neyi başarmaya çalıştığınıza bağlıdır; farklı yöntemler hakkında daha fazla ayrıntı için dokümanlara bakın.


10

Bunu birçok şekilde başarabiliriz.

Kullanımı Kernel#execBu komut sonrasında, hiçbir şey:

exec('ls ~')

kullanma backticks or %x

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

Kullanılması Kernel#system, döner komutunu truebaşarılı olursa falsebaşarısız olursa ve döner nilkomut yürütme başarısız olursa:

system('ls ~')
=> true


9

Buradaki cevapları kullanarak ve Mihai'nin cevabına bağlı olarak, bu gereksinimleri karşılayan bir işlevi bir araya getirdim:

  1. STDOUT ve STDERR'i düzgün bir şekilde yakalar, böylece betiğim konsoldan çalıştırıldığında "sızmaz".
  2. Bağımsız değişkenlerin kabuğa bir dizi olarak iletilmesine izin verir, bu nedenle kaçış konusunda endişelenmenize gerek yoktur.
  3. Komutun çıkış durumunu yakalar, böylece bir hata oluştuğunda netleşir.

Bonus olarak, bu, shell komutunun başarılı bir şekilde (0) çıktığı ve STDOUT'a herhangi bir şey koyduğu durumlarda STDOUT'u döndürür. Bu şekilde, bu tür durumlarda systemgeri dönen farklıdır true.

Kod aşağıdadır. Özel fonksiyon 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"

9

spawnBelirtilen komutu yürütmek için bir arka plan işlemi oluşturma komutunu unutmayın . ProcessSınıfı ve iade edileni kullanarak tamamlanmasını bekleyebilirsiniz 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 diyor ki: Bu yöntem benzer #systemancak komutun bitmesini beklemez.


2
Kernel.spawn()diğer tüm seçeneklerden çok daha çok yönlü görünüyor.
Kashyap

6

Eğer ele alınamayan yaygın vakadan daha karmaşık bir durumunuz varsa ``, o zaman kontrol edin Kernel.spawn(). Bu, harici komutları yürütmek için hisse senedi Ruby tarafından sağlanan en genel / tam özellikli gibi görünüyor.

Bunu aşağıdakiler için kullanabilirsiniz:

  • işlem grupları yarat (Windows).
  • içeri / dışarı, hata dosyalarına / birbirlerine yönlendirme.
  • set env vars, umask.
  • bir komutu yürütmeden önce dizini değiştirin.
  • CPU / veri / vb. için kaynak limitleri belirleyin.
  • Diğer yanıtlardaki diğer seçeneklerle yapılabilecek her şeyi, ancak daha fazla kodla yapın.

Yakut dokümantasyon yeterince iyi örnekler vardır:

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)

6

Backticks (`) yöntemi, Ruby'den kabuk komutlarını çağırmanın en kolay yöntemidir. Shell komutunun sonucunu döndürür:

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

5

Gibi bir komut verildi attrib:

require 'open3'

a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
  puts stdout.read
end

Ben bu yöntem kadar unutulmaz olmasa da buldum

system("thecommand")

veya

`thecommand`

backticks, bu yöntem hakkında diğer yöntemlere göre iyi bir şey backticks bana putsçalıştırmak çalıştırmak / bir değişkente çalıştırmak istediğiniz komutu saklamak ve system("thecommand")bana çıktı almak izin vermiyor gibi görünüyor bu yöntem her ikisini de yapmama izin veriyor ve stdin, stdout ve stderr'a bağımsız olarak erişmeme izin veriyor.

Bkz. " Komutları ruby'de yürütme " ve Ruby'nin Open3 belgeleri .


3

Bu gerçekten bir cevap değil ama belki birisi faydalı bulabilir:

TK GUI'yi Windows'ta kullanırken ve rubyw'den kabuk komutlarını çağırmanız gerekiyorsa, her zaman bir saniyeden daha kısa bir süre için can sıkıcı bir CMD penceresi açılır.

Bundan kaçınmak için şunları kullanabilirsiniz:

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

veya

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

Her ikisi de ipconfigçıktıyı içeride depolarlog.txt , ancak hiçbir pencere çıkmaz.

İhtiyacın olacak require 'win32ole'Senaryonun içine girmen .

system(), exec()Ve spawn()TK ve rubyw kullanırken bütün bu can sıkıcı pencereyi açılır.


-2

İşte OS X üzerinde bir yakut komut dosyasında kullandığım harika bir şey (böylece bir komut dosyasını başlatabilir ve pencereden uzaklaştıktan sonra bile bir güncelleme alabilirim):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.