Obtener la salida de las llamadas al sistema () en Ruby

309

Si llamo a un comando usando el sistema Kernel # en Ruby, ¿cómo obtengo su salida?

system("ls")
Macha
fuente
1
Es posible que desee echar un vistazo a este hilo en comp.lang.ruby
Manrico Corazzi
Este es un hilo muy manual, gracias. La clase para ejecutar comandos y obtener comentarios es excelente en el código de muestra.
iluminar
3
Para futuros googlers. Si desea obtener información sobre otras llamadas de comando del sistema y sus diferencias, consulte esta respuesta SO .
Uzbekjon

Respuestas:

347

Me gustaría ampliar y aclarar un poco la respuesta del caos .

Si rodea su comando con backticks, entonces no necesita llamar (explícitamente) a system () en absoluto. Los backticks ejecutan el comando y devuelven la salida como una cadena. Luego puede asignar el valor a una variable así:

output = `ls`
p output

o

printf output # escapes newline chars
Craig Walker
fuente
44
¿Qué pasa si necesito dar una variable como parte de mi comando? Es decir, ¿en qué se traduciría algo como el sistema ("ls" + nombre de archivo) cuando se van a utilizar los backticks?
Vijay Dev
47
Usted puede hacer la evaluación de expresiones tal como lo haría con las cadenas regulares: ls #{filename}.
Craig Walker
36
Esta respuesta no es aconsejable: presenta el nuevo problema de la entrada de usuario no higienizada.
Dogweather
44
@Dogweather: eso puede ser cierto, pero ¿es diferente de cualquiera de los otros métodos?
Craig Walker
20
si desea captar stderr simplemente ponga 2> & 1 al final de su comando. por ejemplo, salida =command 2>&1
micred
243

¡Tenga en cuenta que todas las soluciones a las que pasa una cadena que contiene valores proporcionados por el usuario system, %x[]etc., no son seguras! Inseguro en realidad 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 systemy Open3.popen3proporcionar una variante segura / de escape en Ruby 1.8. En Ruby 1.9 IO::popentambién acepta una matriz.

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 que probablemente quiera 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 el formulario de bloqueo cerrará automáticamente stdin, stdout y stderr; de lo contrario, tendrían que cerrarse explícitamente .

Más información aquí: Formación de comandos de shell sanitarios o llamadas al sistema en Ruby

Simon Hürlimann
fuente
26
Esta es la única respuesta que realmente responde a la pregunta y resuelve el problema sin introducir otras nuevas (entrada no higiénica).
Dogweather
2
¡Gracias! Este es el tipo de respuesta que esperaba. Una corrección: las getsllamadas deben pasar el argumento nil, ya que de lo contrario solo obtendremos la primera línea de la salida. Entonces, por ejemplo stdout.gets(nil).
Greg Price
3
stdin, stdout y stderr deben cerrarse explícitamente en forma no bloqueada .
Yarin
¿Alguien sabe si algo cambió en Ruby 2.0 o 2.1? Ediciones o comentarios serían apreciados ;-)
Simon Hürlimann
1
Creo que en la discusión Open3.popen3falta un problema importante: si tiene un subproceso que escribe más datos en stdout de los que puede contener una tubería, el subproceso se suspende stderr.writey su programa se atasca stdout.gets(nil).
hagello
165

Solo para el registro, si desea ambos (resultado de salida y operación) puede hacer:

output=`ls no_existing_file` ;  result=$?.success?
FernandoFabreti
fuente
44
Esto es exactamente lo que estaba buscando. Gracias.
jdl
12
Eso solo captura stdout, y stderr va a la consola. Para obtener stderr, use: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept
8
Esta respuesta no es segura y no debe usarse: si el comando no es una constante, entonces la sintaxis de retroceso puede causar un error, posiblemente una vulnerabilidad de seguridad. (E incluso si es una constante, probablemente hará que alguien lo use por una no constante más tarde y cause un error). Vea la respuesta de Simon Hürlimann para obtener una solución correcta.
Greg Price
23
Felicitaciones a Greg Price por comprender la necesidad de escapar de la entrada del usuario, pero no es correcto decir que esta respuesta como escrita no es segura. El método Open3 mencionado es más complicado e introduce más dependencias, y el argumento de que alguien "lo usará para una no constante más tarde" es un hombre de paja. Es cierto que probablemente no los usaría en una aplicación Rails, pero para un script de utilidad de sistema simple sin posibilidad de entrada del usuario no confiable, los backticks están perfectamente bien y nadie debe sentirse mal por usarlos.
sbeam
69

La forma más sencilla de hacer esto correctamente y de forma segura es utilizar Open3.capture2(), Open3.capture2e()o Open3.capture3().

Usar los backticks de ruby ​​y su %xalias NO SON SEGUROS BAJO NINGUNA CIRCUNSTANCIA si se usan con datos no confiables. Es PELIGROSO , así de simple:

untrusted = "; date; echo"
out = `echo #{untrusted}`                              # BAD

untrusted = '"; date; echo"'
out = `echo "#{untrusted}"`                            # BAD

untrusted = "'; date; echo'"
out = `echo '#{untrusted}'`                            # BAD

La systemfunción, por el contrario, escapa a los 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 más adecuadas para la tarea. Open3.capture2, capture2ey capture3funciona 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

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 mayor comodidad, puede ajustar Open3.capture3()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')
Denis de Bernardy
fuente
2
Esta es la respuesta correcta. También es el más informativo. Lo único que falta es una advertencia sobre cerrar los std * s. Vea este otro comentario : require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } tenga en cuenta que el formulario de bloqueo se cerrará automáticamente stdin, stdout y stderr; de lo contrario, tendrían que cerrarse explícitamente .
Peter H. Boling
@ PeterH.Boling: Mejor yo sepa, el capture2, capture2ey capture3también cerca de ellos STD * s automáticamente. (Por lo menos, nunca me encontré con el problema de mi parte.)
Denis de Bernardy
sin usar el formulario de bloque, no hay forma de que una base de código sepa cuándo se debe cerrar algo, por lo que dudo mucho que se esté cerrando. Probablemente nunca haya tenido un problema porque no cerrarlos no causará problemas en un proceso de corta duración, y si reinicia un proceso de ejecución prolongada con la suficiente frecuencia, otto tampoco aparecerá allí a menos que esté abriendo std * s en un bucle. Linux tiene un límite alto de descriptor de archivo, que puede alcanzar, pero hasta que no lo vea no verá el "error".
Peter H. Boling
2
@ PeterH.Boling: No, no, vea el código fuente. Las funciones son solo envoltorios Open3#popen2, popen2ey popen3con un bloque predefinido: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/…
Denis de Bernardy
1
@Dennis de Barnardy Quizás te perdiste que vinculé a la misma documentación de clase (aunque para Ruby 2.0.0, y un método diferente. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… Del ejemplo : `` `stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = wait_thr [: pid] # pid del proceso iniciado ... stdin.close # stdin , stdout y stderr deben cerrarse explícitamente en este formulario. stdout.close stderr.close `` `` Estaba citando la documentación ''. # stdin, stdout y stderr deberían cerrarse explícitamente en este formulario "
Peter H. Boling
61

Puede usar system () o% x [] dependiendo del tipo de resultado que necesite.

system () devuelve verdadero si el comando se encontró y se ejecutó correctamente, de lo contrario, falso.

>> 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 de blog de Jay Fields explica en detalle las diferencias entre usar system, exec y% x [..].

Martin Gross
fuente
2
Gracias por el consejo de usar% x []. Simplemente resolvió un problema que tenía cuando usaba ticks en un script ruby ​​en Mac OS X. Cuando ejecuté el mismo script en una máquina Windows con Cygwin, falló debido a los ticks posteriores, pero funcionó con% x [].
Henrik Warne
22

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"
Lri
fuente
¡Gracias! Esto es perfecto.
Greg Price
21

Usas backticks:

`ls`
caos
fuente
55
Los backticks no producen salida en la terminal.
Mei
3
No produce stderr pero da stdout.
Nickolay Kondratenko
1
No escribe en stdout o stderr. Probemos este ejemplo ruby -e '%x{ls}': tenga en cuenta que no hay salida. (FYI %x{}es equivalente a backticks.)
Ocodo
Esto funcionó muy bien. El uso shreflejaría la salida a la consola (es decir, STDOUT) y la devolvería. Esto no lo hace.
Joshua Pinter
19

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 alimentar datos en la entrada estándar del programa, así como leer su salida estándar.

dwc
fuente
Solo usé esto para leer la salida estándar de un comando aws cli para leer el json y no el valor de retorno oficial de 'true'
kraftydevil
14

Descubrí que lo siguiente es útil si necesita el valor de retorno:

result = %x[ls]
puts result

Específicamente quería 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]
Geoff van der Meer
fuente
Es una gran solución.
Ronan Louarn
13

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 el formulario de bloqueo cerrará automáticamente stdin, stdout y stderr; de lo contrario, tendrían que cerrarse explícitamente .

Yarin
fuente
9

Si bien usar backticks o popen es a menudo lo que realmente quieres, en realidad no responde la pregunta que se hace. Puede haber razones válidas para capturar systemresultados (tal vez para pruebas automatizadas). Un pequeño Google encontró una respuesta que pensé que publicaría aquí en beneficio de los demás.

Como necesitaba esto para probar, mi ejemplo utiliza una configuración de bloque para capturar la salida estándar ya que la systemllamada real 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

Este método captura 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

Puede reemplazar la systemllamada con cualquier cosa que llame internamente system. También podría usar un método similar para capturar stderrsi lo desea.

Eric Anderson
fuente
8

Si desea que la salida se redirija a un archivo utilizando Kernel#system, puede modificar descriptores como este:

redirigir stdout y stderr a un archivo (/ tmp / log) en modo append:

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 el resultado utilizando un IO.pipe y redirigirlo desde el sistema Kernel #.

Ashrith
fuente
-1
puts `date`
puts $?


Mon Mar  7 19:01:15 PST 2016
pid 13093 exit 0
Ron Hinchley
fuente