Obtenir la sortie des appels système() dans Ruby
si j'appelle une commande en utilisant Kernel#system dans Ruby, Comment puis-je obtenir sa sortie?
system("ls")
15 réponses
j'aimerais développer et clarifier un peu la réponse du chaos .
si vous Encerclez votre commande avec des backticks, alors vous n'avez pas besoin d'appeler (explicitement) system() du tout. Les bascules exécutent la commande et renvoient la sortie sous forme de chaîne. Vous pouvez alors assigner la valeur à une variable comme ainsi:
output = `ls`
p output
ou
printf output # escapes newline chars
soyez conscient que toutes les solutions où vous passez une chaîne contenant des valeurs fournies par l'utilisateur à system
, %x[]
etc. sont dangereux! Unsafe signifie réellement: l'utilisateur peut déclencher le code pour exécuter dans le contexte et avec toutes les permissions du programme.
pour autant que je puisse dire seulement system
et Open3.popen3
fournissent une variante sûre/échappatoire dans Ruby 1.8. Dans Ruby 1.9 IO::popen
accepte également un tableau.
passez simplement toutes les options et l'argument comme un tableau à l'un de ces appels.
si vous avez besoin non seulement de l'état de sortie, mais aussi le résultat, vous voulez 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 le formulaire de bloc fermera automatiquement stdin, stdout et stderr-sinon ils devront être fermés explicitement .
plus d'informations ici: formation de commandes d'enveloppe sanitaire ou appels de système dans Ruby
juste pour information, Si vous voulez les deux (sortie et résultat d'opération) vous pouvez faire:
output=`ls no_existing_file` ; result=$?.success?
vous pouvez utiliser system() ou %x[] selon le type de résultat dont vous avez besoin.
system () retournant true si la commande a été trouvée et exécutée avec succès, false autrement.
>> 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 [..] d'autre part enregistre les résultats de la commande comme une chaîne de caractères:
>> 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 post de blog par Jay Champs explique en détail les différences entre l'utilisation du système, exec et %x[..] .
la façon simple de le faire correctement et en toute sécurité est d'utiliser Open3.capture2()
, Open3.capture2e()
, ou Open3.capture3()
.
utilisant les bâtons de ruby et son %x
alias sont non sécurisé en aucun cas si utilisé avec des données non fiables. C'est dangereux , simple 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 aux arguments correctement si elle est utilisée correctement :
ret = system "echo #{untrusted}" # BAD
ret = system 'echo', untrusted # good
le problème est, il retourne le code de sortie au lieu de la sortie, et la capture de cette dernière est alambiquée et désordonnée.
La meilleure réponse dans ce fil jusqu'à présent mentionne Open3, mais pas les fonctions qui sont le mieux à la tâche. Open3.capture2
, capture2e
et capture3
travail comme system
, mais retourne 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 mention IO.popen()
. La syntaxe peut être maladroite dans le sens où elle veut un tableau comme 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 le suivant:
nil
nil
false
false
/usr/bin/which <— stdout from system('which', 'which')
true <- p system('which', 'which')
"/usr/bin/which" <- p syscall('which', 'which')
une autre voie est:
f = open("|ls")
foo = f.read()
notez que c'est le caractère" pipe "devant" ls " à l'air libre. Cela peut également être utilisé pour alimenter les données dans l'entrée standard des programmes ainsi que la lecture de sa sortie standard.
Si vous avez besoin d'échapper les arguments, en Ruby 1.9 IO.popen accepte aussi un tableau:
p IO.popen(["echo", "it's escaped"]).read
dans les versions précédentes, 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 dans 1.9 et 1.8:
out = IO.popen("xxd -p", "r+") { |io|
io.print "xyz"
io.close_write
io.read.chomp
}
p out # "78797a"
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 PID de tous les processus Java sur ma machine, et j'ai utilisé ceci:
ids = %x[ps ax | grep java | awk '{ print }' | xargs]
comme Simon Hürlimann déjà expliqué , Open3 est plus sûr que des bâtons, etc.
require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
notez que le formulaire de bloc fermera automatiquement stdin, stdout et stderr-sinon ils devront être fermés explicitement .
si vous voulez que la sortie soit redirigée vers un fichier en utilisant Kernel#system
, vous pouvez modifier les descripteurs comme ceci:
rediriger stdout et stderr vers un fichier (/tmp / log) en mode ajout:
system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])
pour une commande de longue durée, cela stockera la sortie en temps réel. Vous pouvez également, stocker la sortie en utilisant un IO.pipe et redirection à partir du noyau#system.
en utilisant des bâtons arrière ou popen est souvent ce que vous voulez vraiment, il ne répond pas réellement à la question posée. Il peut y avoir des raisons valables pour capturer la sortie system
(peut-être pour des tests automatisés). Un peu Googling trouvé une réponse j'ai pensé que je posterais ici pour le bénéfice des autres.
puisque j'en avais besoin pour tester mon exemple utilise une configuration de bloc pour capturer la sortie standard puisque l'appel réel system
est enfoui 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
donc cela nous donne une méthode qui va capturer n'importe quelle sortie dans le bloc donné en utilisant un fichier tempfile pour stocker les données réelles. Exemple d'utilisation:
captured_content = capture_stdout do
system 'echo foo'
end
puts captured_content
bien sûr, vous pouvez remplacer l'appel system
par tout ce qui pourrait appeler en interne system
. Vous pouvez également utiliser la même méthode pour capturer stderr si vous le souhaitez.
en tant que système direct(...) remplacement vous pouvez utiliser Open3.popen3(...)
suite de la discussion: 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 la pleine production.
vous pouvez rediriger STDERR vers STDOUT si vous voulez capturer STDERR en utilisant backtick.
output = ' grep hosts / private / etc / * 2> & 1`
source: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
puts `date`
puts $?
Mon Mar 7 19:01:15 PST 2016
pid 13093 exit 0