tutorial - ruby system command
Llamando comandos de shell desde Ruby (20)
Algunas cosas para pensar al elegir entre estos mecanismos son:
- ¿Solo quieres stdout o necesitas stderr también? o incluso separados?
- ¿Qué tan grande es su salida? ¿Quieres mantener todo el resultado en la memoria?
- ¿Desea leer algo de su salida mientras el subproceso todavía se está ejecutando?
- ¿Necesita códigos de resultados?
- ¿Necesita un objeto de rubí que represente el proceso y le permita matarlo a demanda?
Es posible que necesite cualquier cosa, desde simples backticks (``), system () y
IO.popen
hasta
Kernel.fork
/
Kernel.exec
con
IO.pipe
y
IO.select
IO.pipe
IO.select
.
También es posible que desee agregar tiempos de espera a la mezcla si un subproceso tarda demasiado en ejecutarse.
Desafortunadamente, depende mucho.
¿Cómo invoco comandos de shell desde dentro de un programa Ruby? ¿Cómo puedo obtener la salida de estos comandos de nuevo en Ruby?
Aquí hay una genial que uso en una secuencia de comandos ruby en OS X (para que pueda iniciar una secuencia de comandos y obtener una actualización incluso después de haber salido de la ventana):
cmd = %Q|osascript -e ''display notification "Server was reset" with title "Posted Update"''|
system ( cmd )
Dado un comando, por ejemplo, atrib
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, el sistema ("comando") o el comando en backticks, algo bueno acerca de este método en comparación con otros métodos ... es que, por ejemplo, Backticks no me deja "poner ''el comando Ejecuto / almaceno el comando que deseo ejecutar en una variable, y el sistema ("comando") no parece permitirme obtener la salida. 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
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)
Esta explicación se basa en un guión de Ruby comentado por 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
Bash.
Alguna sintaxis de Bash no es compatible con
/bin/sh
en todos los sistemas.
Aquí hay formas de ejecutar un script de shell:
cmd = "echo ''hi''" # Sample string that can be used
-
Kernel#`
, comúnmente llamado`cmd`
Esto es como muchos otros idiomas, incluidos Bash, PHP y Perl.
Devuelve el resultado del comando de shell.
Docs: http://ruby-doc.org/core/Kernel.html#method-i-60
value = `echo ''hi''` value = `#{cmd}`
-
Sintaxis incorporada,
%x( cmd )
Seguir el carácter
x
es un delimitador, que puede ser cualquier carácter. Si el delimitador es uno de los caracteres(
,[
,{
, o<
, el literal consiste en los caracteres hasta el delimitador de cierre coincidente, teniendo en cuenta los pares delimitadores anidados. Para todos los demás delimitadores, el literal comprende los caracteres hasta el Próxima aparición del carácter delimitador. Se permite la interpolación de cadena#{ ... }
.Devuelve el resultado del comando de shell, al igual que las comillas invertidas.
Docs: http://www.ruby-doc.org/docs/ProgrammingRuby/html/language.html
value = %x( echo ''hi'' ) value = %x[ #{cmd} ]
-
Kernel#system
Ejecuta el comando dado en una subshell.
Devuelve
true
si el comando fue encontrado y ejecutado exitosamente,false
contrario.Docs: 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.
Docs: 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 del sistema si usas las comillas invertidas,
system()
o
%x{}
.
A continuación, puede acceder a las propiedades
exitstatus
y
pid
:
$?.exitstatus
Para más lectura ver:
Este es el mejor artículo en mi opinión sobre la ejecución de scripts de shell en Ruby: " http://tech.natemurray.com/2007/03/ruby-shell-commands.html ".
Si solo necesitas obtener la salida usa backticks.
Necesitaba cosas más avanzadas como STDOUT y STDERR, así que usé la gema Open4. Tienes todos los métodos explicados allí.
La forma en que me gusta hacer esto es usar
%x
literal, 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, llenará 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
La forma más fácil es, por ejemplo:
reboot = `init 6`
puts reboot
Las respuestas anteriores ya son bastante buenas, pero realmente quiero compartir el siguiente artículo de resumen: " http://tech.natemurray.com/2007/03/ruby-shell-commands.html "
Básicamente, nos dice:
Kernel#exec
:
exec ''echo "hello $HOSTNAME"''
system
y
$?
:
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 joya:
require "open4"
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]
Mi favorito es Open3
require "open3"
Open3.popen3(''nroff -man'') { |stdin, stdout, stderr| ... }
No es realmente una respuesta, pero quizás a alguien le resulte útil, y se trata de esto.
Al usar TK GUI en Windows, y necesitas llamar a los comandos de shell desde rubyw, u siempre tendrá una ventana de cmd molesta que aparecerá por 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á U
require ''win32ole''
dentro de su script.
system()
,
exec()
y
spawn()
mostrarán esa ventana molesta cuando usen TK y rubyw.
No olvide el comando
spawn
para crear un proceso en segundo plano para ejecutar el comando especificado.
Incluso puede esperar su finalización utilizando la clase de
Process
y el
pid
devuelto:
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 a
#system
pero no espera a que finalice el comando.
Podemos lograrlo de múltiples maneras.
Usando
Kernel#exec
, nada después de ejecutar este comando:
exec(''ls ~'')
Usando
backticks or %x
`ls ~`
=> "Applications/nDesktop/nDocuments"
%x(ls ~)
=> "Applications/nDesktop/nDocuments"
Usando
Kernel#system
comando del
Kernel#system
, devuelve
true
si es exitoso,
false
si no tiene éxito y devuelve
nil
si la ejecución del comando falla:
system(''ls ~'')
=> true
Si realmente necesitas Bash, por la nota en la "mejor" respuesta.
Primero, tenga en cuenta que cuando Ruby llama a un shell, normalmente llama
/bin/sh
, no Bash. Alguna sintaxis de Bash no es compatible con/bin/sh
en 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 (por ejemplo,
script_output = system("./my_script.sh")
) Ruby
debe
cumplir con el shebang, pero siempre puede usar el
system("bash ./my_script.sh")
para asegurarse ( aunque puede haber una ligera sobrecarga de
/bin/sh
running
/bin/bash
, probablemente no lo notará.
Si tiene un caso más complejo que el caso común (que no se puede manejar con
``
), consulte
Kernel.spawn()
here
.
Este parece ser el producto más genérico / completo proporcionado por
Ruby
para ejecutar comandos externos.
Por ejemplo, puedes usarlo para:
- crear grupos de procesos (Windows)
- redirigir dentro, fuera, error a archivos / entre sí.
- establecer env vars, umask
- cambiar dir antes de ejecutar comando
- establecer limites de recursos para CPU / data / ...
- Haga todo lo que se puede hacer con otras opciones en otras respuestas, pero con más código.
La here oficial de here tiene buenos 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)
También puede usar los operadores de comillas invertidas (`), similares a Perl:
directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory
Práctico si necesitas algo simple.
El método que desee utilizar depende exactamente de lo que intenta lograr; Consulte la documentación para obtener más detalles acerca de los diferentes métodos.
Una opción más:
Cuando tú:
- Necesito stderr así como stdout
- No puedo / no usaré Open3 / Open4 (lanzan excepciones en NetBeans en mi Mac, ni idea de por qué)
Puede utilizar 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 sintaxis
2>&1
funciona en
Linux
, Mac y
Windows
desde los primeros días de MS-DOS.
Usando las respuestas aquí y enlazadas en la respuesta de Mihai, armé una función que cumple con estos requisitos:
- Captura perfectamente STDOUT y STDERR para que no se "escapen" cuando mi script se ejecuta desde la consola.
- Permite que los argumentos se pasen al shell como una matriz, por lo que no hay que preocuparse por escapar.
- 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 ponga cualquier cosa en STDOUT.
De esta manera, difiere del
system
, que simplemente devuelve
true
en 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"
-
El método de 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}`