Obtener la salida de las llamadas a system () en Ruby
Si llamo a un comando usando Kernel#system en Ruby, ¿cómo obtengo su salida?
system("ls")
15 answers
Me gustaría ampliar y aclarar la respuesta de chaos un poco.
Si rodeas tu comando con backsticks, entonces no necesitas llamar (explícitamente) a system() en absoluto. Los backsticks ejecutan el comando y devuelven la salida como una cadena. A continuación, puede asignar el valor a una variable de la siguiente manera:
output = `ls`
p output
O
printf output # escapes newline chars
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:30
Tenga en cuenta que todas las soluciones en las que pasa una cadena que contiene valores proporcionados por el usuario asystem
, %x[]
etc. son inseguros! Unsafe en realidad significa: el usuario puede activar el código para que se ejecute en el contexto y con todos los permisos del programa.
Por lo que puedo decir, solo system
y Open3.popen3
proporcionan una variante segura/de escape en Ruby 1.8. En Ruby 1.9 IO::popen
también acepta una matriz.
Simplemente pase cada opción y argumento como una matriz a una de estas llamadas.
Si no necesitas solo el estado de salida, pero también el resultado que probablemente desee usar Open3.popen3
:
require 'open3'
stdin, stdout, stderr, wait_thr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets(nil)
stdout.close
stderr.gets(nil)
stderr.close
exit_code = wait_thr.value
Tenga en cuenta que la forma de bloque cerrará automáticamente stdin, stdout y stderr-de lo contrario tendrían que cerrarse explícitamente.
Más información aquí: Forming sanitary shell commands or system calls in Ruby
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:44
Solo para el registro, si desea ambos (salida y resultado de la operación) puede hacer:
output=`ls no_existing_file` ; result=$?.success?
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-01 15:25:10
Puede usar system() o %x [] dependiendo del tipo de resultado que necesite.
System() devuelve true si el comando se encontró y se ejecutó correctamente, false en caso contrario.
>> s = system 'uptime'
10:56 up 3 days, 23:10, 2 users, load averages: 0.17 0.17 0.14
=> true
>> s.class
=> TrueClass
>> $?.class
=> Process::Status
% x [..] por otro lado guarda los resultados del comando como una cadena:
>> result = %x[uptime]
=> "13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> p result
"13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> result.class
=> String
Th blog post by Jay Fields explica en detalle las diferencias entre el uso de system, exec y %x[..] .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-25 16:04:59
La manera sencilla de hacer esto de forma correcta y segura es el uso de Open3.capture2()
, Open3.capture2e()
, o Open3.capture3()
.
Usar backsticks de ruby y su alias %x
son NO SEGUROS BAJO NINGUNA CIRCUNSTANCIA si se usan con datos no confiables. Es PELIGROSO , simple y llanamente:
untrusted = "; date; echo"
out = `echo #{untrusted}` # BAD
untrusted = '"; date; echo"'
out = `echo "#{untrusted}"` # BAD
untrusted = "'; date; echo'"
out = `echo '#{untrusted}'` # BAD
La función system
, por el contrario, escapa argumentos correctamente si se usa correctamente :
ret = system "echo #{untrusted}" # BAD
ret = system 'echo', untrusted # good
El problema es que devuelve el código de salida de la salida, y la captura de este último es enrevesado y desordenado.
La mejor respuesta en este hilo hasta ahora menciona Open3, pero no las funciones que son las más adecuadas para la tarea. Open3.capture2
, capture2e
y capture3
funciona como system
, pero devuelve dos o tres argumentos:
out, err, st = Open3.capture3("echo #{untrusted}") # BAD
out, err, st = Open3.capture3('echo', untrusted) # good
out_err, st = Open3.capture2e('echo', untrusted) # good
out, st = Open3.capture2('echo', untrusted) # good
p st.exitstatus
Otra mención IO.popen()
. La sintaxis puede ser torpe en el sentido de que quiere una matriz como entrada, pero también funciona:
out = IO.popen(['echo', untrusted]).read # good
Para mayor comodidad, puede envolver Open3.capture3()
en una función, por ejemplo:
#
# Returns stdout on success, false on failure, nil on error
#
def syscall(*cmd)
begin
stdout, stderr, status = Open3.capture3(*cmd)
status.success? && stdout.slice!(0..-(1 + $/.size)) # strip trailing eol
rescue
end
end
Ejemplo:
p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')
Produce lo siguiente:
nil
nil
false
false
/usr/bin/which <— stdout from system('which', 'which')
true <- p system('which', 'which')
"/usr/bin/which" <- p syscall('which', 'which')
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-06 13:49:44
Usted usa backsticks:
`ls`
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-27 15:17:37
Otra forma es:
f = open("|ls")
foo = f.read()
Tenga en cuenta que es el carácter "pipe" antes de "ls" en open. Esto también se puede utilizar para alimentar datos en la entrada estándar de los programas, así como la lectura de su salida estándar.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-27 15:23:20
Si necesita escapar los argumentos, en Ruby 1.9 IO.popen también acepta una matriz:
p IO.popen(["echo", "it's escaped"]).read
En versiones anteriores se puede usar Open3.popen3 :
require "open3"
Open3.popen3("echo", "it's escaped") { |i, o| p o.read }
Si también necesita pasar stdin, esto debería funcionar tanto en 1.9 como en 1.8:
out = IO.popen("xxd -p", "r+") { |io|
io.print "xyz"
io.close_write
io.read.chomp
}
p out # "78797a"
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-20 04:27:09
Encontré que lo siguiente es útil si necesita el valor devuelto:
result = %x[ls]
puts result
Específicamente quería enumerar los pids de todos los procesos Java en mi máquina, y usé esto:
ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-16 05:43:40
Como Simon Hürlimann ya explicó, Open3 es más seguro que backsticks, etc.
require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
Tenga en cuenta que el formulario de bloque cerrará automáticamente stdin, stdout y stderr-de lo contrario tendrían que cerrarse explícitamente.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:27
Si desea que la salida se redirija a un archivo usando Kernel#system
, puede modificar descriptores como este:
Redirigir stdout y stderr a un archivo (/tmp / log) en modo anexar:
system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])
Para un comando de larga ejecución, esto almacenará la salida en tiempo real. También puede almacenar la salida usando una IO.pipe y redireccionarlo desde Kernel # system.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-26 04:58:41
Si bien usar backsticks o popen es a menudo lo que realmente quieres, en realidad no responde a la pregunta formulada. Puede haber razones válidas para capturar la salida system
(tal vez para pruebas automatizadas). Un poco de Googleo apareció una respuesta Pensé que publicaría aquí para el beneficio de los demás.
Dado que necesitaba esto para probar, mi ejemplo usa una configuración de bloque para capturar la salida estándar ya que la llamada real system
está enterrada en el código que se está probando:
require 'tempfile'
def capture_stdout
stdout = $stdout.dup
Tempfile.open 'stdout-redirect' do |temp|
$stdout.reopen temp.path, 'w+'
yield if block_given?
$stdout.reopen stdout
temp.read
end
end
Así que esto nos da un método que capturará cualquier salida en el bloque dado usando un tempfile para almacenar los datos reales. Ejemplo de uso:
captured_content = capture_stdout do
system 'echo foo'
end
puts captured_content
Por supuesto, puede reemplazar la llamada system
con cualquier cosa que pueda llamar internamente system
. También puede usar el mismo método para capturar stderr si lo desea.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-16 22:47:47
Como un sistema directo(...) reemplazo usted puede utilizar Open3.popen3(...)
Debate adicional: http://tech.natemurray.com/2007/03/ruby-shell-commands.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-04 14:24:58
No encontré este aquí, así que agregándolo, tuve algunos problemas para obtener la salida completa.
Puede redirigir STDERR a STDOUT si desea capturar STDERR utilizando backtick.
Output ` ' grep hosts / private / etc/* 2>&1`
Fuente: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-27 15:39:15
puts `date`
puts $?
Mon Mar 7 19:01:15 PST 2016
pid 13093 exit 0
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-08 03:02:26