Llamar comandos de shell desde Ruby
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?
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
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}`
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} ]
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 )
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.
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:
- ¿Solo quieres stdout o también necesitas stderr?o incluso separado?
- ¿Qué tan grande es su producción?¿Quieres mantener todo el resultado en la memoria?
- ¿Desea leer parte de su salida mientras el subproceso aún se está ejecutando?
- ¿Necesita códigos de resultados?
- ¿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:
- Captura perfectamente STDOUT y STDERR para que no se "filtren" cuando mi script se ejecuta desde la consola.
- Permite pasar argumentos al shell como una matriz, por lo que no hay necesidad de preocuparse por el escape.
- 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 )