Chiamare comandi della shell da Ruby
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?
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
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}`
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} ]
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 )
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.
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:
- Vuoi solo stdout o hai anche bisogno di stderr?o anche separato?
- Quanto è grande la tua produzione?Vuoi mantenere l'intero risultato in memoria?
- Vuoi leggere alcuni dei tuoi output mentre il sottoprocesso è ancora in esecuzione?
- Hai bisogno di codici risultato?
- 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:
- Cattura chiaramente STDOUT e STDERR in modo che non "perdano" quando il mio script viene eseguito dalla console.
- Consente di passare gli argomenti alla shell come array, quindi non è necessario preoccuparsi dell'escape.
- 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 )