web-dev-qa-db-fra.com

Obtenir la sortie des appels system () en Ruby

Si j'appelle une commande à l'aide de Kernel # system dans Ruby, comment puis-je obtenir sa sortie?

system("ls")
291
Macha

Je voudrais développer et clarifier la réponse du chaos un peu.

Si vous entourez votre commande de backticks, vous n'avez pas besoin d'appeler explicitement system (). Les backticks exécutent la commande et renvoient la sortie sous forme de chaîne. Vous pouvez ensuite affecter la valeur à une variable comme ceci:

output = `ls`
p output

ou

printf output # escapes newline chars
333
Craig Walker

Sachez que toutes les solutions dans lesquelles vous passez une chaîne contenant des valeurs fournies par l'utilisateur à system, %x[] etc. ne sont pas sûres! Non sécurisé signifie en réalité que l'utilisateur peut déclencher le code pour qu'il s'exécute dans le contexte et avec toutes les autorisations du programme.

Pour autant que je sache, seules les variables system et Open3.popen3 fournissent une variante sécurisée/d'échappement dans Ruby 1.8. En Ruby 1.9, IO::popen accepte également un tableau.

Passez simplement chaque option et argument sous forme de tableau à l'un de ces appels.

Si vous avez besoin non seulement du statut de sortie, mais également du résultat, vous souhaitez probablement utiliser 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

Notez que la forme du bloc fermera automatiquement stdin, stdout et stderr - sinon, ils devront être fermés explicitement .

Plus d'informations ici: Formation de commandes Shell sanitaires ou d'appels système en Ruby

228
Simon Hürlimann

Pour mémoire, si vous voulez les deux (résultat de sortie et résultat de l'opération), vous pouvez faire:

output=`ls no_existing_file` ;  result=$?.success?
162
FernandoFabreti

Le moyen le plus simple de procéder correctement et en toute sécurité consiste à utiliser Open3.capture2() , Open3.capture2e() ou Open3.capture3() .

L'utilisation des backticks de Ruby et de son alias %x est NOT SECURE SOUS TOUTES CIRCONSTANCES si elle est utilisée avec des données non fiables. C'est DANGEREUX, clair et simple:

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

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

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

La fonction system, en revanche, échappe correctement aux arguments si elle est utilisée correctement:

ret = system "echo #{untrusted}"                       # BAD
ret = system 'echo', untrusted                         # good

Le problème, c'est que le code de sortie est renvoyé à la place de la sortie et que la capture de cette dernière est compliquée et compliquée.

La meilleure réponse dans ce fil mentionne jusqu'à présent Open3, mais pas les fonctions les mieux adaptées à la tâche. Open3.capture2, capture2e et capture3 fonctionnent comme system, mais renvoie deux ou trois arguments:

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

Une autre mentionne IO.popen() . La syntaxe peut être maladroite dans le sens où elle veut un tableau en entrée, mais cela fonctionne aussi:

out = IO.popen(['echo', untrusted]).read               # good

Pour plus de commodité, vous pouvez envelopper Open3.capture3() dans une fonction, par exemple:

#
# 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

Exemple:

p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')

Donne ce qui suit:

nil
nil
false
false
/usr/bin/which         <— stdout from system('which', 'which')
true                   <- p system('which', 'which')
"/usr/bin/which"       <- p syscall('which', 'which')
59
Denis de Bernardy

Vous pouvez utiliser system () ou% x [] en fonction du type de résultat souhaité.

system () renvoie true si la commande a été trouvée et exécutée avec succès, false sinon.

>> 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 [..] en revanche enregistre les résultats de la commande sous forme de chaîne:

>> 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 de Jay Fields explique en détail les différences entre using system, exec et% x [..].

59
Martin Gross

Une autre façon est:

f = open("|ls")
foo = f.read()

Notez que c'est le caractère "pipe" avant "ls" dans open. Ceci peut également être utilisé pour introduire des données dans l'entrée standard du programme ainsi que pour lire sa sortie standard.

19
dwc

Si vous avez besoin d'échapper aux arguments, dans Ruby 1.9 IO.popen accepte également un tableau:

p IO.popen(["echo", "it's escaped"]).read

Dans les versions antérieures, vous pouvez utiliser Open3.popen3 :

require "open3"

Open3.popen3("echo", "it's escaped") { |i, o| p o.read }

Si vous devez également passer stdin, cela devrait fonctionner à la fois en 1.9 et en 1.8:

out = IO.popen("xxd -p", "r+") { |io|
    io.print "xyz"
    io.close_write
    io.read.chomp
}
p out # "78797a"
19
Lri

Vous utilisez des backticks:

`ls`
19
chaos

J'ai trouvé que ce qui suit est utile si vous avez besoin de la valeur de retour:

result = %x[ls]
puts result

Je voulais spécifiquement lister les pids de tous les processus Java sur ma machine, et je l'ai utilisé

ids = %x[ps ax | grep Java | awk '{ print $1 }' | xargs]
13

Comme Simon Hürlimann l’a déjà expliqué , Open3 est plus sûr que les backticks, etc. 

require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }

Notez que la forme du bloc fermera automatiquement stdin, stdout et stderr, sinon ils devront être fermés explicitement .

11
Yarin

Si vous souhaitez que la sortie redirige vers un fichier à l'aide de Kernel#system, vous pouvez modifier les descripteurs de la manière suivante:

redirigez stdout et stderr vers un fichier (/ tmp/log) en mode ajout:

system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])

Pour une longue commande, cela stockera la sortie en temps réel. Vous pouvez également stocker la sortie à l'aide d'un fichier IO.pipe et la rediriger depuis Kernel # system.

8
Ashrith

Si vous voulez vraiment utiliser des backticks ou des popen, cela ne répond pas vraiment à la question posée. Il peut y avoir des raisons valables pour capturer la sortie system (peut-être pour un test automatisé). Un peu de Google a trouvé une réponse Je pensais que je posterais ici pour le bénéfice des autres. 

Comme j'avais besoin de cela pour tester, mon exemple utilise une configuration de bloc pour capturer la sortie standard car l'appel actuel system est enterré dans le code à tester:

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

Cette méthode capture toute sortie du bloc donné en utilisant un fichier temporaire pour stocker les données réelles. Exemple d'utilisation:

captured_content = capture_stdout do
  system 'echo foo'
end
puts captured_content

Vous pouvez remplacer l'appel system par tout ce qui appelle en interne system. Vous pouvez également utiliser une méthode similaire pour capturer stderr si vous le souhaitez.

8
Eric Anderson

En remplacement direct du système, vous pouvez utiliser Open3.popen3 (...)

Discussion complémentaire: http://tech.natemurray.com/2007/03/Ruby-Shell-commands.html

7

Je n'ai pas trouvé celui-ci ici, donc en l'ajoutant, j'ai eu quelques problèmes pour obtenir le résultat complet. 

Vous pouvez rediriger STDERR vers STDOUT si vous souhaitez capturer STDERR à l'aide de backtick.

sortie = `hôtes grep/private/etc/* 2> & 1`

source: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-Ruby.html

0
dac2009