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")
Author: Per Lundberg, 2009-03-27

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
 325
Author: Craig Walker,
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

 224
Author: Simon Hürlimann,
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?
 160
Author: FernandoFabreti,
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[..] .

 58
Author: Martin Gross,
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 %xson 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')
 57
Author: Denis de Bernardy,
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`
 19
Author: chaos,
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.

 19
Author: dwc,
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"
 19
Author: user495470,
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]
 13
Author: Geoff van der Meer,
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.

 10
Author: Yarin,
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.

 8
Author: Ashrith,
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.

 7
Author: Eric Anderson,
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

 6
Author: Antonin Hildebrand,
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

 0
Author: dac2009,
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
 -1
Author: Ron Hinchley,
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