Cómo llamar a comandos de shell desde Ruby

1077

¿Cómo llamo a los comandos de shell desde el interior de un programa Ruby? ¿Cómo obtengo el resultado de estos comandos nuevamente en Ruby?

CodificaciónSin Comentarios
fuente
3
Si bien esta pregunta es útil, no se hace bien. Ruby tiene muchas formas de llamar subcapas que están bien documentadas y se encuentran fácilmente leyendo la documentación de Kernel y Open3 y buscando aquí en SO.
The Tin Man el
1
Lamentablemente este tema es bastante complejo. Open3( docs ) es la mejor opción para la mayoría de las situaciones, IMO, pero en versiones anteriores de Ruby, no respetará un modificado PATH( bugs.ruby-lang.org/issues/8004 ), y dependiendo de cómo pase los argumentos (específicamente , si utiliza hash opts con palabras clave que no son), puede romperse. Pero, si te encuentras con esas situaciones, estás haciendo algo bastante avanzado y puedes descubrir qué hacer leyendo la implementación de Open3.
Joshua Cheek
3
Me sorprende que nadie haya mencionado Shellwords.escape( doc ). No querrá insertar la entrada del usuario directamente en los comandos de la shell: ¡escape primero! Ver también comando inyección .
Kelvin

Respuestas:

1319

Esta explicación se basa en un script Ruby comentado de un amigo mío. Si desea mejorar el script, no dude en actualizarlo en el enlace.

Primero, tenga en cuenta que cuando Ruby llama a un shell, generalmente llama /bin/sh, no Bash. Alguna sintaxis de Bash no es compatible /bin/shcon todos los sistemas.

Aquí hay formas de ejecutar un script de shell:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` , comúnmente llamados backticks - `cmd`

    Esto es como muchos otros lenguajes, incluidos Bash, PHP y Perl.

    Devuelve el resultado (es decir, salida estándar) del comando de shell.

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

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

    Siguiendo al xpersonaje hay un delimitador, que puede ser cualquier personaje. Si el delimitador es uno de los personajes (, [, {, o <, el literal consiste en los caracteres hasta el delimitador de cierre coincidente, teniendo en cuenta los pares de delimitadores anidados. Para todos los demás delimitadores, el literal comprende los caracteres hasta la próxima aparición del carácter delimitador. #{ ... }Se permite la interpolación de cadenas .

    Devuelve el resultado (es decir, la salida estándar) del comando de shell, al igual que los backticks.

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

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

    Ejecuta el comando dado en una subshell.

    Devuelve truesi el comando se encontró y se ejecutó correctamente, de lo falsecontrario.

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

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

    Reemplaza el proceso actual ejecutando el comando externo dado.

    No devuelve ninguno, el proceso actual se reemplaza y nunca continúa.

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

Aquí hay algunos consejos adicionales: $?que es lo mismo que $CHILD_STATUS, accede al estado del último comando ejecutado por el sistema si usa las teclas de retroceso, system()o %x{}. Luego puede acceder a las propiedades exitstatusy pid:

$?.exitstatus

Para más lectura ver:

Steve Willard
fuente
44
Necesito registrar las salidas de mi ejecutable en el servidor de producción pero no encontré ninguna manera. Solía pone #{cmd}y logger.info ( #{cmd}). ¿Hay alguna forma de registrar sus resultados en la producción?
Omer Aslam
55
Y IO # popen () y Open3 # popen3 (). mentalized.net/journal/2010/03/08/…
hughdbrown
66
En aras de la integridad (como pensé por primera vez, esto también sería un comando Ruby): Rake tiene sh que hace "Ejecutar el comando del sistema cmd. Si se dan varios argumentos, el comando no se ejecuta con el shell (la misma semántica que Kernel :: exec y Kernel :: system) ".
sschuberth
40
Los backticks no capturan STDERR por defecto. Agregue `2> & 1` al comando si desea capturar
Andrei Botalov
14
Creo que esta respuesta se mejoraría ligeramente si dijera que los backticks y% x devolvieron el "resultado", en lugar del "resultado", del comando dado. Este último podría confundirse con el estado de salida. O solo soy yo?
skagedal
275

Aquí hay un diagrama de flujo basado en " Cuándo usar cada método para iniciar un subproceso en Ruby ". Vea también, " Engañe a una aplicación para que piense que su stdout es un terminal, no una tubería ".

ingrese la descripción de la imagen aquí

Ian
fuente
24
Wow jaja. Muy útil aunque el hecho de que esto tenga que existir es lamentable
Josh Bodah
Como nota al margen, creo que el método de desove () encuentra en muchos lugares diferentes (por ejemplo, Kernely Processpara ser más versátil Es más o menos lo mismo con. PTY.spawn(), Pero más genérico.
Smar
160

La forma en que me gusta hacer esto es usar el %xliteral, lo que hace que sea fácil (¡y legible!) Usar comillas en un comando, así:

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

Que, en este caso, completará la lista de archivos con todos los archivos de prueba en el directorio actual, que puede procesar como se espera:

directorylist.each do |filename|
  filename.chomp!
  # work with file
end
cínico
fuente
44
¿Te %x[ cmd ]devuelve una matriz?
x-yuri
2
Lo anterior no me funciona. `` <principal> '': método indefinido, each' for :String (NoMethodError) ¿cómo funcionó para usted? Estoy usando ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]¿Está seguro de que el comando devuelve una matriz para que el bucle realmente funcione?
Nasser
% x [cmd] .split ( "\ n") devolverá una lista aunque :)
Ian Ellis
65

Aquí está el mejor artículo en mi opinión sobre la ejecución de scripts de shell en Ruby: " 6 formas de ejecutar comandos de Shell en Ruby ".

Si solo necesita obtener la salida, utilice las teclas de retroceso.

Necesitaba cosas más avanzadas como STDOUT y STDERR, así que usé la gema Open4. Tienes todos los métodos explicados allí.

Mihai A
fuente
2
La publicación descrita aquí no discute la %xopción de sintaxis.
Mei
+1 para Open4. Ya había comenzado a tratar de implementar mi propia versión de su spawnmétodo cuando encontré esto.
Brandan
40

Mi favorito es Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
anshul
fuente
3
También me gusta open3, especialmente Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
severin
¿Existe alguna documentación sobre cómo realizar pruebas de especificaciones y unidades con Open3 u otros Open en Ruby std-lib? Es difícil probar los shell en mi nivel actual de comprensión.
FilBot3
29

Algunas cosas para pensar al elegir entre estos mecanismos son:

  1. ¿Solo quieres stdout o necesitas stderr también? O incluso separado?
  2. ¿Qué tan grande es su producción? ¿Quieres guardar todo el resultado en la memoria?
  3. ¿Desea leer parte de su salida mientras el subproceso aún se está ejecutando?
  4. ¿Necesita códigos de resultado?
  5. ¿Necesita un objeto Ruby que represente el proceso y le permita matarlo a pedido?

Es posible que necesite cualquier cosa, desde simples backticks (``) system(), y IO.popenhasta completo Kernel.fork/ Kernel.execcon IO.pipey IO.select.

También puede agregar tiempos de espera en la mezcla si un subproceso tarda demasiado en ejecutarse.

Lamentablemente, depende mucho .

Nick Brosnahan
fuente
25

Una opción mas:

Cuando usted:

  • necesita stderr y stdout
  • no puedo / no usaré Open3 / Open4 (arrojan excepciones en NetBeans en mi Mac, no tengo idea de por qué)

Puede usar la redirección de 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>&1sintaxis funciona en Linux , Mac y Windows desde los primeros días de MS-DOS.

jg-faustus
fuente
25

Definitivamente no soy un experto en Ruby, pero lo intentaré:

$ irb 
system "echo Hi"
Hi
=> true

También deberías poder hacer cosas como:

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

Las respuestas anteriores ya son bastante buenas, pero realmente quiero compartir el siguiente artículo de resumen: " 6 formas de ejecutar comandos de Shell en Ruby "

Básicamente, nos dice:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

systemy $?:

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 -- una gema:

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

Si realmente necesita Bash, según la nota en la "mejor" respuesta.

Primero, tenga en cuenta que cuando Ruby llama a un shell, generalmente llama /bin/sh, no Bash. Alguna sintaxis de Bash no es compatible /bin/shcon todos los sistemas.

Si necesita usar Bash, inserte bash -c "your Bash-only command"dentro del método de llamada deseado:

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

Probar:

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

O si está ejecutando un archivo de script existente como

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

Ruby debería honrar al shebang, pero siempre puedes usar

system("bash ./my_script.sh")

para asegurarse, aunque puede haber una ligera sobrecarga de /bin/shejecución /bin/bash, probablemente no lo note.

dragon788
fuente
11

También puede usar los operadores de retroceso (`), similares a Perl:

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

Útil si necesitas algo simple.

El método que desea utilizar depende exactamente de lo que está tratando de lograr; Consulte los documentos para obtener más detalles sobre los diferentes métodos.

Rufo Sanchez
fuente
10

Podemos lograrlo de múltiples maneras.

Usando Kernel#exec, nada después de ejecutar este comando:

exec('ls ~')

Utilizando backticks or %x

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

Usando el Kernel#systemcomando, devuelve truesi tiene éxito, falsesi no tiene éxito y devuelve nilsi falla la ejecución del comando:

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

La forma más fácil es, por ejemplo:

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

Usando las respuestas aquí y enlazadas en la respuesta de Mihai, armé una función que cumple con estos requisitos:

  1. Captura perfectamente STDOUT y STDERR para que no se "filtren" cuando mi script se ejecuta desde la consola.
  2. Permite que los argumentos se pasen al shell como una matriz, por lo que no hay necesidad de preocuparse por escapar.
  3. Captura el estado de salida del comando para que quede claro cuando se produjo un error.

Como beneficio adicional, este también devolverá STDOUT en los casos en que el comando de shell salga con éxito (0) y coloque cualquier cosa en STDOUT. De esta manera, difiere de system, lo que simplemente regresa trueen tales casos.

El código sigue. La función específica es 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
fuente
9

No olvide el spawncomando para crear un proceso en segundo plano para ejecutar el comando especificado. Incluso puede esperar a que se complete usando la Processclase y el devuelto 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

El documento dice: Este método es similar #systempero no espera a que termine el comando.

MonsieurDart
fuente
2
Kernel.spawn()Parece ser mucho más versátil que todas las demás opciones.
Kashyap
6

Si tiene un caso más complejo que el caso común con el que no se puede manejar ``, consulte Kernel.spawn(). Este parece ser el más genérico / con todas las funciones proporcionado por stock Ruby para ejecutar comandos externos.

Puedes usarlo para:

  • crear grupos de procesos (Windows).
  • redirigir entrada, salida, error a archivos / entre sí.
  • establecer env vars, umask.
  • cambie el directorio antes de ejecutar un comando.
  • establecer límites de recursos para CPU / datos / etc.
  • Haga todo lo que se pueda hacer con otras opciones en otras respuestas, pero con más código.

La documentación de Ruby tiene suficientes ejemplos:

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
fuente
6

El método backticks (`) es el más fácil para llamar a comandos de shell desde Ruby. Devuelve el resultado del comando de shell:

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

Dado un comando como attrib:

require 'open3'

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

He descubierto que si bien este método no es tan memorable como

system("thecommand")

o

`thecommand`

en backticks, lo bueno de este método en comparación con otros métodos es que los backticks no parecen permitirme putsel comando que ejecuto / guardo el comando que quiero ejecutar en una variable, y system("thecommand")no parece permitirme obtener el resultado mientras que Este método me permite hacer ambas cosas y me permite acceder a stdin, stdout y stderr de forma independiente.

Consulte " Ejecución de comandos en ruby " y la documentación de Ruby's Open3 .

barlop
fuente
3

Esto no es realmente una respuesta, pero tal vez alguien lo encuentre útil:

Cuando use TK GUI en Windows, y necesite llamar a comandos de shell desde rubyw, siempre tendrá una ventana CMD molesta que aparecerá por menos de un segundo.

Para evitar esto, puede usar:

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

o

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

Ambos almacenarán la ipconfigsalida en el interior log.txt, pero no aparecerán ventanas.

Necesitará require 'win32ole'dentro de su script.

system(), exec()y spawn()todos aparecerán esa ventana molesta cuando utilices TK y rubyw.

lucaortis
fuente
-2

Aquí hay uno genial que uso en un script de ruby ​​en OS X (para que pueda iniciar un script y obtener una actualización incluso después de alejarme de la ventana):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
JayCrossler
fuente