Domanda

Come posso chiamare i comandi della shell dall'interno di un programma Ruby?Come posso quindi riportare l'output di questi comandi in Ruby?

È stato utile?

Soluzione

Questa spiegazione si basa su un commento Scrittura rubino da un mio amico.Se vuoi migliorare lo script, sentiti libero di aggiornarlo al link.

Innanzitutto, tieni presente che quando Ruby chiama una shell, in genere chiama /bin/sh, non Bash.Alcune sintassi Bash non sono supportate da /bin/sh su tutti i sistemi.

Ecco i modi per eseguire uno script di shell:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` , comunemente chiamati apici inversi – `cmd`

    Questo è come molti altri linguaggi, inclusi Bash, PHP e Perl.

    Restituisce il risultato del comando shell.

    Documenti: http://ruby-doc.org/core/Kernel.html#method-i-60

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

    Seguendo il x il carattere è un delimitatore, che può essere qualsiasi carattere.Se il delimitatore è uno dei caratteri (, [, {, O <, il letterale è costituito dai personaggi fino al delimitatore di chiusura corrispondente, tenendo conto delle coppie delimitanti nidificate.Per tutti gli altri delimitatori, il letterale comprende i personaggi fino alla prossima occorrenza del carattere delimitatore.Interpolazione di stringhe #{ ... } È permesso.

    Restituisce il risultato del comando shell, proprio come i backtick.

    Documenti: http://www.ruby-doc.org/docs/ProgrammingRuby/html/lingua.html

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

    Esegue il comando dato in una subshell.

    ritorna true se il comando è stato trovato ed eseguito correttamente, false Altrimenti.

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

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

    Sostituisce il processo corrente eseguendo il comando esterno fornito.

    Non restituisce nessuno, il processo corrente viene sostituito e non continua mai.

    Documenti: 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
    

Ecco qualche consiglio in più:$?, che è lo stesso di $CHILD_STATUS, accede allo stato dell'ultimo comando eseguito dal sistema se si utilizzano i backtick, system() O %x{}.È quindi possibile accedere al exitstatus E pid proprietà:

$?.exitstatus

Per ulteriori letture vedere:

Altri suggerimenti

Ecco un diagramma di flusso basato su questa risposta.Guarda anche, utilizzando script per emulare un terminale.

enter image description here

Il modo in cui mi piace farlo è usare il file %x letterale, che rende facile (e leggibile!) l'uso delle virgolette in un comando, in questo modo:

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

Che, in questo caso, popolerà l'elenco dei file con tutti i file di test nella directory corrente, che potrai elaborare come previsto:

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

Ecco il miglior articolo secondo me sull'esecuzione degli script di shell in Ruby:"6 modi per eseguire comandi Shell in Ruby".

Se hai solo bisogno di ottenere l'output, usa i backtick.

Avevo bisogno di cose più avanzate come STDOUT e STDERR quindi ho usato la gemma Open4.Hai tutti i metodi spiegati lì.

Il mio preferito è Apri3

  require "open3"

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

Alcune cose a cui pensare quando si sceglie tra questi meccanismi sono:

  1. Vuoi solo stdout o hai anche bisogno di stderr?o anche separato?
  2. Quanto è grande la tua produzione?Vuoi mantenere l'intero risultato in memoria?
  3. Vuoi leggere alcuni dei tuoi output mentre il sottoprocesso è ancora in esecuzione?
  4. Hai bisogno di codici risultato?
  5. Hai bisogno di un oggetto Ruby che rappresenti il ​​processo e ti consenta di ucciderlo su richiesta?

Potresti aver bisogno di qualsiasi cosa, dai semplici apici inversi (``), system() e IO.popen a pieno titolo Kernel.fork/Kernel.exec con IO.pipe E IO.select.

Potresti anche voler inserire dei timeout nel mix se un sottoprocesso impiega troppo tempo per essere eseguito.

Sfortunatamente, moltissimo dipende.

Un'altra opzione:

Quando tu:

  • è necessario stderr e stdout
  • non posso/non userò Open3/Open4 (generano eccezioni in NetBeans sul mio Mac, non ho idea del perché)

Puoi utilizzare il reindirizzamento della shell:

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

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

IL 2>&1 la sintassi funziona in modo trasversale Linux, Mac e finestre fin dai primi giorni di MS-DOS.

Sicuramente non sono un esperto di Ruby, ma ci proverò:

$ irb 
system "echo Hi"
Hi
=> true

Dovresti anche essere in grado di fare cose come:

cmd = 'ls'
system(cmd)

Le risposte sopra sono già abbastanza interessanti, ma voglio davvero condividere il seguente articolo di riepilogo:"6 modi per eseguire comandi Shell in Ruby"

In sostanza ci dice:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

system E $?:

system 'false' 
puts $?

Apice inverso (`):

today = `date`

IO#popen:

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

Open3#popen3 --libbstd:

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

Open4#popen4 -- una gemma:

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

Se hai davvero bisogno di Bash, secondo la nota nella risposta "migliore".

Innanzitutto, tieni presente che quando Ruby chiama una shell, in genere chiama /bin/sh, non Bash.Alcune sintassi Bash non sono supportate da /bin/sh su tutti i sistemi.

Se devi usare Bash, inserisci bash -c "your Bash-only command" all'interno del metodo di chiamata desiderato.

quick_output = system("ls -la")

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

Testare:

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

Oppure se stai eseguendo un file di script esistente (ad es script_output = system("./my_script.sh")) Rubino Dovrebbe onora la faccenda, ma potresti sempre usarla system("bash ./my_script.sh") per essere sicuri (anche se potrebbe esserci un leggero sovraccarico da /bin/sh corsa /bin/bash, probabilmente non te ne accorgerai.

Puoi anche utilizzare gli operatori backtick (`), simili a Perl:

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

Comodo se hai bisogno di qualcosa di semplice.

Il metodo che desideri utilizzare dipende esattamente da ciò che stai cercando di ottenere;controlla la documentazione per maggiori dettagli sui diversi metodi.

il modo più semplice è, ad esempio:

reboot = `init 6`
puts reboot

Utilizzando le risposte qui e collegate nella risposta di Mihai, ho messo insieme una funzione che soddisfa questi requisiti:

  1. Cattura chiaramente STDOUT e STDERR in modo che non "perdano" quando il mio script viene eseguito dalla console.
  2. Consente di passare gli argomenti alla shell come array, quindi non è necessario preoccuparsi dell'escape.
  3. Cattura lo stato di uscita del comando in modo che sia chiaro quando si è verificato un errore.

Come bonus, questo restituirà anche STDOUT nei casi in cui il comando shell esce con successo (0) e inserisce qualcosa su STDOUT.In questo modo differisce da system, che semplicemente ritorna true in tali casi.

Segue il codice.La funzione specifica è 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"

Possiamo ottenerlo in molteplici modi.

Utilizzando Kernel#exec, non viene eseguito nulla dopo l'esecuzione di questo comando:

exec('ls ~')

Utilizzando backticks or %x

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

Utilizzando Kernel#system comando, ritorna true in caso di successo, false in caso di insuccesso e ritorna nil se l'esecuzione del comando fallisce:

system('ls ~')
=> true

Non dimenticare il spawn comando per creare un processo in background per eseguire il comando specificato.Puoi anche attendere il suo completamento utilizzando il file Process classe e il restituito 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

Il dottore dice:Questo metodo è simile a #system ma non attende il completamento del comando.

Se hai un caso più complesso del caso comune (che non può essere gestito ``) poi controlla Kernel.spawn() Qui.Questo sembra essere il più generico/completo fornito da magazzino Rubino per eseguire comandi esterni.

Per esempio.puoi usarlo per:

  • creare gruppi di processi (Windows)
  • reindirizzare dentro, fuori, errore su file/reciprocamente.
  • imposta env vars, umask
  • cambia dir prima di eseguire il comando
  • imposta i limiti delle risorse per CPU/dati/...
  • Fai tutto ciò che può essere fatto con altre opzioni in altre risposte, ma con più codice.

Ufficiale documentazione rubino ha esempi abbastanza buoni.

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)
  • Il metodo backticks ` è il più semplice per chiamare i comandi della shell da Ruby.Restituisce il risultato del comando shell.

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

Dato un comando, ad esempio attrib

require 'open3'

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

Ho scoperto che sebbene questo metodo non sia così memorabile come ad es.system("thecommand") o thecommand in backtick, un aspetto positivo di questo metodo rispetto ad altri metodi..è ad es.i backtick non sembrano permettermi di "mettere" il comando che eseguo/memorizzare il comando che voglio eseguire in una variabile e system ("thecommand") non sembra consentirmi di ottenere l'output.Mentre questo metodo mi consente di fare entrambe le cose e di accedere a stdin, stdout e stderr in modo indipendente.

https://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html

http://ruby-doc.org/stdlib-2.4.1/libdoc/open3/rdoc/Open3.html

Non proprio una risposta, ma forse qualcuno lo troverà utile e riguarda questo.

Quando usi la GUI TK su Windows e devi chiamare i comandi della shell da Rubyw, avrai sempre una fastidiosa finestra cmd che apparirà per meno di un secondo.

Per evitare questo puoi usare

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

O

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

Entrambi memorizzeranno l'output di ipconfig all'interno di "log.txt", ma non verrà visualizzata alcuna finestra.

Ne avrai bisogno require 'win32ole' all'interno della tua sceneggiatura.

system(), exec() E spawn() verrà visualizzata quella fastidiosa finestra quando si utilizzano TK e Rubyw.

Eccone uno interessante che utilizzo in uno script Ruby su OS X (in modo da poter avviare uno script e ottenere un aggiornamento anche dopo essermi allontanato dalla finestra):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top