Si j'appelle une commande à l'aide de Kernel # system dans Ruby, comment puis-je obtenir sa sortie?
system("ls")
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
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
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?
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')
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 [..].
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.
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"
Vous utilisez des backticks:
`ls`
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]
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 .
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.
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.
En remplacement direct du système, vous pouvez utiliser Open3.popen3 (...)
Discussion complémentaire: http://tech.natemurray.com/2007/03/Ruby-Shell-commands.html
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