Pregunta

¿Cómo llamo comandos de shell desde dentro de un programa Ruby?¿Cómo puedo entonces recuperar el resultado de estos comandos en Ruby?

¿Fue útil?

Solución

Esta explicación se basa en un comentario escritura rubí 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, normalmente llama /bin/sh, no Intento.Algunas sintaxis de Bash no son compatibles con /bin/sh en todos los sistemas.

A continuación se muestran formas de ejecutar un script de shell:

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

    Es como muchos otros lenguajes, incluidos Bash, PHP y Perl.

    Devuelve el resultado 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 el x El carácter es un delimitador, que puede ser cualquier carácter.Si el delimitador es uno de los caracteres. (, [, {, o <, El literal consiste en los personajes hasta el delimitador de cierre de igual, teniendo en cuenta los pares de delimitadores anidados.Para todos los demás delimitadores, el literal comprende los personajes hasta la siguiente ocurrencia del carácter delimitador.Interpolación de cadenas #{ ... } esta permitido.

    Devuelve el resultado del comando de shell, al igual que las comillas invertidas.

    Documentos: http://www.ruby-doc.org/docs/ProgrammingRuby/html/language.html

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

    Ejecuta el comando dado en un subshell.

    Devoluciones true si el comando se encontró y se ejecutó correctamente, false de lo contrario.

    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 utiliza las comillas invertidas, system() o %x{}.Luego podrá acceder al exitstatus y pid propiedades:

$?.exitstatus

Para más lecturas ver:

Otros consejos

Aquí hay un diagrama de flujo basado en esta respuesta.Ver también, usando script para emular una terminal.

enter image description here

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

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

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

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

En mi opinión, este es el mejor artículo sobre la ejecución de scripts de shell en Ruby:"6 formas de ejecutar comandos de Shell en Ruby".

Si solo necesita obtener el resultado, utilice comillas invertidas.

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

Mi favorito es Abierto3

  require "open3"

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

Algunas cosas en las que pensar al elegir entre estos mecanismos son:

  1. ¿Solo quieres stdout o también necesitas stderr?o incluso separado?
  2. ¿Qué tan grande es su producción?¿Quieres mantener 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 resultados?
  5. ¿Necesitas un objeto Ruby que represente el proceso y te permita matarlo a pedido?

Es posible que necesite cualquier cosa, desde simples comillas invertidas (``), sistema() y IO.popen en toda regla Kernel.fork/Kernel.exec con IO.pipe y IO.select.

También es posible que desee incluir tiempos de espera en la combinación si un subproceso tarda demasiado en ejecutarse.

Desafortunadamente, mucho depende.

Una opción más:

Cuando usted:

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

Puedes 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"

El 2>&1 la sintaxis funciona a través de linux, Mac y ventanas desde los primeros días de MS-DOS.

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)

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

Básicamente nos dice:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

system y $?:

system 'false' 
puts $?

Comillas invertidas (`):

today = `date`

IO#popen:

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

Open3#popen3 -- biblioteca estándar:

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

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

Primero, tenga en cuenta que cuando Ruby llama a un shell, normalmente llama /bin/sh, no Intento.Algunas sintaxis de Bash no son compatibles con /bin/sh en todos los sistemas.

Si necesita usar Bash, inserte bash -c "your Bash-only command" dentro de su 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 (por ejemplo script_output = system("./my_script.sh")) rubí debería honra el shebang, pero siempre puedes usar system("bash ./my_script.sh") para asegurarse (aunque puede haber una ligera sobrecarga debido a /bin/sh correr /bin/bash, probablemente no lo notarás.

También puedes utilizar los operadores de comillas invertidas (`), similares a Perl:

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

Útil si necesitas algo simple.

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

La forma más sencilla es, por ejemplo:

reboot = `init 6`
puts reboot

Usando las respuestas aquí y vinculadas en la respuesta de Mihai, preparé 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 pasar argumentos al shell como una matriz, por lo que no hay necesidad de preocuparse por el escape.
  3. Captura el estado de salida del comando para que quede claro cuando se ha producido un error.

Como beneficio adicional, este también devolverá STDOUT en los casos en que el comando de shell salga correctamente (0) y coloque algo en STDOUT.De esta manera, se diferencia de system, que simplemente devuelve true en esos 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"

Podemos lograrlo de múltiples maneras.

Usando Kernel#exec, no se ejecuta nada después de este comando:

exec('ls ~')

Usando backticks or %x

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

Usando Kernel#system comando, regresa true si tiene éxito, false si no tiene éxito y regresa nil si la ejecución del comando falla:

system('ls ~')
=> true

No olvides el spawn comando para crear un proceso en segundo plano para ejecutar el comando especificado.Incluso puedes esperar a que se complete usando el Process clase y el regreso 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 doctor dice:Este método es similar a #system pero no espera a que finalice el comando.

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

P.ej.puedes usarlo para:

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

Oficial documentación rubí tiene ejemplos bastante buenos.

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)
  • El método backticks `es el más fácil de llamar a comandos de shell desde Ruby.Devuelve el resultado del comando shell.

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

Dado un comando, por ejemplo, attrib

require 'open3'

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

Descubrí que si bien este método no es tan memorable como, por ejemplo,system("thecommand") o el comando entre comillas invertidas, algo bueno de este método en comparación con otros métodos.es por ej.Las comillas invertidas no parecen permitirme 'poner' el comando que ejecuto/almacenar el comando que quiero ejecutar en una variable, y el sistema ("thecommand") no parece dejarme 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.

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

Realmente no es una respuesta, pero tal vez a alguien le resulte útil y se refiere a esto.

Cuando usas TK GUI en Windows y necesitas llamar comandos de shell desde rubyw, siempre aparecerá una molesta ventana cmd durante menos de un segundo.

Para evitar esto puedes 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 salida de ipconfig dentro de 'log.txt', pero no aparecerá ninguna ventana.

necesitarás require 'win32ole' dentro de tu guión.

system(), exec() y spawn() Aparecerá esa molesta ventana al usar TK y Rubyw.

Aquí hay uno interesante que uso en un script Ruby en OS X (para poder iniciar un script y obtener una actualización incluso después de salir de la ventana):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top