Come chiamare i comandi di shell da Ruby


1077

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


3
Sebbene questa domanda sia utile, non viene posta bene. Ruby ha molti modi per chiamare sub-shell ben documentate e facilmente reperibili leggendo la documentazione di Kernel e Open3 e cercando qui su SO.
Tin Man,

1
Purtroppo questo argomento è piuttosto complesso. Open3( docs ) è la scelta migliore per la maggior parte delle situazioni, IMO, ma nelle versioni precedenti di Ruby, non rispetterà un modificato PATH( bugs.ruby-lang.org/issues/8004 ) e in base al modo in cui passi args (in particolare , se si utilizza l'hash opts con non parole chiave), può interrompersi. Ma, se colpisci quelle situazioni, stai facendo qualcosa di piuttosto avanzato e puoi capire cosa fare leggendo l'implementazione di Open3.
Joshua Cheek,

3
Sono sorpreso che nessuno abbia menzionato Shellwords.escape( doc ). Non vuoi inserire l'input dell'utente direttamente nei comandi della shell: scappa prima! Vedi anche comando di iniezione .
Kelvin,

Risposte:


1319

Questa spiegazione si basa su uno script Ruby commentato da un mio amico. Se vuoi migliorare lo script, sentiti libero di aggiornarlo sul link.

Innanzitutto, nota che quando Ruby chiama una shell, in genere chiama /bin/sh, non Bash. Una certa sintassi di Bash non è supportata da /bin/shtutti i sistemi.

Ecco i modi per eseguire uno script di shell:

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

    Questo è come molte altre lingue, tra cui Bash, PHP e Perl.

    Restituisce il risultato (ovvero l'output standard) del comando shell.

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

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

    Seguire il xpersonaggio è un delimitatore, che può essere qualsiasi personaggio. Se il delimitatore è uno dei caratteri (, [, {, o <, letterale costituito dai caratteri alla delimitatore di chiusura corrispondente, tenendo conto di coppie di delimitatori annidati. Per tutti gli altri delimitatori, il letterale comprende i caratteri fino alla successiva occorrenza del carattere delimitatore. #{ ... }È consentita l' interpolazione di stringhe .

    Restituisce il risultato (ovvero l'output standard) del comando shell, proprio come i backtick.

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

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

    Esegue il comando dato in una subshell.

    Restituisce truese il comando è stato trovato ed eseguito correttamente, falsealtrimenti.

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

    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 alcuni consigli extra:, $?che è lo stesso $CHILD_STATUS, accede allo stato dell'ultimo comando eseguito dal sistema se si usano i backtick, system()oppure%x{} . È quindi possibile accedere a exitstatusepid proprietà :

$?.exitstatus

Per ulteriori letture vedi:


4
Devo registrare gli output del mio eseguibile sul server di produzione ma non ho trovato alcun modo. Ho usato put #{cmd}e logger.info ( #{cmd}). C'è un modo per registrare i loro output in produzione?
Omer Aslam,

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

6
Per completezza (come ho pensato per la prima volta questo sarebbe anche un comando Ruby): Rake ha sh che fa "Esegui il comando di sistema cmd. Se vengono forniti più argomenti il ​​comando non viene eseguito con la shell (stessa semantica di Kernel :: exec e Kernel :: system) ".
sschuberth,

40
I backtick non acquisiscono STDERR per impostazione predefinita. Aggiungi `2> & 1` al comando se vuoi catturare
Andrei Botalov,

14
Penso che questa risposta sarebbe leggermente migliorata se dicesse che i backtick e% x hanno restituito il "risultato", piuttosto che il "risultato", del comando dato. Quest'ultimo potrebbe essere scambiato per stato di uscita. O sono solo io?
skagedal

275

24
Wow haha. Molto utile anche se il fatto che ciò debba esistere è sfortunato
Josh Bodah,

Come nota a margine, trovo il metodo spawn () trovato in molti luoghi diversi (ad esempio Kernele Processper essere più versatile. È più o meno lo stesso con PTY.spawn(), ma più generico.
Smar

160

Il modo in cui mi piace farlo è usare il valore %xletterale, che rende facile (e leggibile!) Usare le 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 è possibile elaborare come previsto:

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

4
Ti %x[ cmd ]restituisce un array?
x-yuri,

2
quanto sopra non funziona per me. `` <main> ': metodo indefinito each' for :String (NoMethodError) come ha funzionato per te? Sto usando ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]Sei sicuro che un array venga restituito dal comando in modo che il ciclo funzioni davvero?
Nasser,

% x [cmd] .split ("\ n") restituirà comunque un elenco :)
Ian Ellis,

65

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 spiegato tutti i metodi lì.


2
Il post qui descritto non discute l' %xopzione di sintassi.
Mei,

+1 per Open4. Avevo già iniziato a provare a implementare la mia versione del suo spawnmetodo quando l'ho trovato.
Brandan,

40

Il mio preferito è Open3

  require "open3"

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

2
Mi piace anche open3, in particolare Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
severin

Esiste una documentazione su come eseguire i test di specifiche e unità con Open3 o altri Open nella Ruby std-lib? È difficile testare i shell out al mio attuale livello di comprensione.
FilBot3,

29

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

  1. Vuoi solo stdout o hai bisogno anche di stderr? O addirittura separato?
  2. Quanto è grande il tuo output? Vuoi conservare 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 permetta di ucciderlo su richiesta?

Potrebbe essere necessario qualsiasi cosa, da semplici backticks ( ``), system()e IO.popenalla piena regola Kernel.fork/ Kernel.execcon IO.pipeeIO.select .

Potresti anche voler lanciare timeout nel mix se un processo secondario impiega troppo tempo per essere eseguito.

Sfortunatamente, dipende molto .


25

Un'altra opzione:

Quando tu:

  • ho bisogno di stderr e di stdout
  • non riesco / non useranno Open3 / Open4 (generano eccezioni in NetBeans sul mio Mac, non ho idea del perché)

Puoi usare 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"

La 2>&1sintassi funziona su Linux , Mac e Windows sin dai primi tempi di MS-DOS.


25

Non sono assolutamente 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)

21

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

Fondamentalmente ci dice:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

systeme $?:

system 'false' 
puts $?

Backtick (`):

today = `date`

IO#popen:

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

Open3#popen3 - stdlib:

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>]

15

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

Innanzitutto, nota che quando Ruby chiama una shell, in genere chiama /bin/sh, non Bash. Una certa sintassi di Bash non è supportata da /bin/shtutti i sistemi.

Se è necessario utilizzare Bash, inserire 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"')

O se stai eseguendo un file di script esistente come

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

Ruby dovrebbe onorare lo shebang, ma potresti sempre usarlo

system("bash ./my_script.sh")

per essere sicuri, anche se potrebbe esserci un leggero sovraccarico da /bin/shcorsa /bin/bash, probabilmente non lo noterai.


11

Puoi anche usare 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.

Quale metodo vuoi usare dipende esattamente da cosa stai cercando di realizzare; controlla i documenti per maggiori dettagli sui diversi metodi.


10

Possiamo raggiungerlo in più modi.

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

exec('ls ~')

utilizzando backticks or %x

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

Utilizzando il Kernel#systemcomando, restituisce truese ha esito positivo, falsese non ha esito positivo e restituisce nilse l'esecuzione del comando non riesce:

system('ls ~')
=> true

10

Il modo più semplice è, ad esempio:

reboot = `init 6`
puts reboot

9

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

  1. Cattura ordinatamente STDOUT e STDERR in modo che non "perdano" quando il mio script viene eseguito dalla console.
  2. Consente agli argomenti di essere passati alla shell come un array, quindi non è necessario preoccuparsi dell'evasione.
  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 correttamente (0) e inserisce qualsiasi cosa in STDOUT. In questo modo, differisce da system, che semplicemente ritornatrue 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"

9

Non dimenticare il spawncomando per creare un processo in background per eseguire il comando specificato. Puoi persino attendere il suo completamento usando la Processclasse e il reso 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 documento dice: Questo metodo è simile #systemma non attende il completamento del comando.


2
Kernel.spawn()sembra essere molto più versatile di tutte le altre opzioni.
Kashyap,

6

Se hai un caso più complesso del caso comune che non può essere gestito ``, controlla Kernel.spawn() . Questo sembra essere il più generico / completo fornito dallo stock Ruby per eseguire comandi esterni.

Puoi usarlo per:

  • creare gruppi di processi (Windows).
  • reindirizzare dentro, fuori, errore su file / a vicenda.
  • set env vars, umask.
  • cambiare la directory prima di eseguire un comando.
  • impostare limiti di risorse per CPU / dati / ecc.
  • Fai tutto ciò che può essere fatto con altre opzioni in altre risposte, ma con più codice.

La documentazione di Ruby contiene esempi abbastanza validi:

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

Il metodo backticks (`) è il metodo più semplice per chiamare i comandi di shell da Ruby. Restituisce il risultato del comando shell:

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

5

Dato un comando come attrib:

require 'open3'

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

Ho scoperto che mentre questo metodo non è così memorabile come

system("thecommand")

o

`thecommand`

nei backtick, un aspetto positivo di questo metodo rispetto ad altri metodi è che i backtick non sembrano lasciarmi putsil comando che eseguo / archiviare il comando che voglio eseguire in una variabile e system("thecommand")non mi sembra di ottenere l'output mentre questo metodo mi consente di fare entrambe queste cose e mi consente di accedere a stdin, stdout e stderr in modo indipendente.

Vedere " Esecuzione dei comandi in ruby " e documentazione Open3 di Ruby .


3

Questa non è davvero una risposta, ma forse qualcuno lo troverà utile:

Quando usi la GUI di TK su Windows e devi chiamare i comandi di shell da rubyw, avrai sempre una fastidiosa finestra CMD che appare per meno di un secondo.

Per evitare ciò 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' ipconfigoutput all'interno log.txt, ma non verrà visualizzata alcuna finestra.

Dovrai require 'win32ole'all'interno della tua sceneggiatura.

system(), exec()E spawn()saranno tutti aprirà quella finestra fastidioso quando si utilizza TK e rubyw.


-2

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 essere passato dalla finestra):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.