sobre - Obteniendo salida de llamadas al sistema() en Ruby
sintaxis de ruby (15)
Como Simon Hürlimann ya explicó , Open3 es más seguro que los backticks, etc.
require ''open3''
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
Tenga en cuenta que la forma de bloque cerrará automáticamente stdin, stdout y stderr, de lo contrario tendrían que cerrarse explícitamente .
Si invoco un comando usando Kernel#system en Ruby, ¿cómo obtengo su salida?
system("ls")
Como reemplazo directo del sistema (...) puede usar Open3.popen3 (...)
Más discusión: http://tech.natemurray.com/2007/03/ruby-shell-commands.html
Descubrí que lo siguiente es útil si necesita el valor de retorno:
result = %x[ls]
puts result
Quería específicamente enumerar los pids de todos los procesos de Java en mi máquina, y usé esto:
ids = %x[ps ax | grep java | awk ''{ print $1 }'' | xargs]
La forma sencilla de hacerlo de forma correcta y segura es usar Open3.capture2()
, Open3.capture2e()
u Open3.capture3()
.
El uso de backticks de ruby y su alias %x
NO ES SEGURO BAJO NINGUNA CIRCUNSTANCIA si se usa con datos no confiables. Es peligroso , sencillo y simple:
untrusted = "; date; echo"
out = `echo #{untrusted}` # BAD
untrusted = ''"; date; echo"''
out = `echo "#{untrusted}"` # BAD
untrusted = "''; date; echo''"
out = `echo ''#{untrusted}''` # BAD
La función del 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 en lugar de la salida, y capturar este último es complicado y desordenado.
La mejor respuesta en este hilo hasta ahora menciona Open3, pero no las funciones que mejor se adaptan a la tarea. Open3.capture2
, capture2e
y capture3
funcionan como el 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
Otro menciona 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 su comodidad, puede ajustar 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'')
Rinde 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'')
Me gustaría ampliar y aclarar un poco la respuesta del caos .
Si rodea su comando con comillas invertidas, entonces no necesita (explícitamente) llamar al sistema () en absoluto. Los backticks ejecutan el comando y devuelven la salida como una cadena. A continuación, puede asignar el valor a una variable así:
output = `ls`
p output
o
printf output # escapes newline chars
Mientras que usar backticks o popen es a menudo lo que realmente desea, en realidad no responde a la pregunta formulada. Puede haber razones válidas para capturar la salida del system
(tal vez para pruebas automatizadas). Un poco de Google encontró una respuesta que pensé que publicaría aquí para beneficio de los demás.
Como lo necesitaba para probar, mi ejemplo utiliza una configuración de bloque para capturar la salida estándar, ya que la llamada real al system
está oculta 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 archivo temporal 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 al system
con cualquier cosa que pueda llamar internamente al system
. También puede usar el mismo método para capturar stderr si lo desea.
No encontré este aquí, así que al agregarlo, tuve algunos problemas para obtener el resultado completo.
Puede redirigir STDERR a STDOUT si desea capturar STDERR utilizando la función de retroceso.
salida = `grep hosts / private / etc / * 2> & 1`
fuente: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
Otra forma es:
f = open("|ls")
foo = f.read()
Tenga en cuenta que ese es el carácter "pipe" antes de "ls" en abierto. Esto también se puede utilizar para introducir datos en la entrada estándar del programa, así como para leer su salida estándar.
Puede usar system () o% x [] dependiendo del tipo de resultado que necesite.
system () devolviendo verdadero si el comando fue encontrado y ejecutado exitosamente, falso de lo 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
La publicación del blog de Jay Fields explica en detalle las diferencias entre el uso de system, exec y% x [..].
Si desea que la salida se redirija a un archivo utilizando Kernel#system
, puede modificar los descriptores de esta forma:
redirigir stdout y stderr a un archivo (/ tmp / log) en modo agregado:
system(''ls -al'', :out => [''/tmp/log'', ''a''], :err => [''/tmp/log'', ''a''])
Para un comando de ejecución prolongada, esto almacenará la salida en tiempo real. También puede almacenar la salida utilizando un tubo IO y redirigirlo desde el sistema Kernel #.
Si necesita escapar de los argumentos, en Ruby 1.9 IO.popen también acepta una matriz:
p IO.popen(["echo", "it''s escaped"]).read
En versiones anteriores puedes 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"
Solo para el registro, si desea ambos (resultado de salida y operación) puede hacer:
output=`ls no_existing_file` ; result=$?.success?
Tenga en cuenta que todas las soluciones en las que pasa una cadena que contiene los valores proporcionados por el usuario al system
, %x[]
etc. no son seguras. Inseguro realmente significa: el usuario puede activar el código para ejecutarse en el contexto y con todos los permisos del programa.
Por lo que puedo decir, solo el system
y Open3.popen3
proporcionan una variante segura / de escape en Ruby 1.8. En Ruby 1.9 IO::popen
también acepta un array.
Simplemente pase cada opción y argumento como una matriz a una de estas llamadas.
Si no solo necesita el estado de salida, sino también el resultado, probablemente desee utilizar 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í: Formando comandos de shell sanitario o llamadas de sistema en Ruby
Utiliza backticks:
`ls`
puts `date`
puts $?
Mon Mar 7 19:01:15 PST 2016
pid 13093 exit 0