Si j'appelle une commande en utilisant le système Kernel # dans Ruby, comment puis-je obtenir sa sortie?
system("ls")
Si j'appelle une commande en utilisant le système Kernel # dans Ruby, comment puis-je obtenir sa sortie?
system("ls")
Réponses:
Je voudrais développer et clarifier un peu la réponse du chaos .
Si vous entourez votre commande de backticks, vous n'avez pas du tout 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
ls #{filename}
.
command 2>&1
Sachez que toutes les solutions où vous passez une chaîne contenant des valeurs fournies par l'utilisateur à system
, %x[]
etc. ne sont pas sécuritaires! Dangereux signifie en fait: l'utilisateur peut déclencher le code à exécuter dans le contexte et avec toutes les autorisations du programme.
Pour autant que je puisse dire seulement system
et Open3.popen3
fournir une variante sécurisée / d'échappement dans Ruby 1.8. Dans 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 aussi du résultat que 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 le formulaire de bloc fermera automatiquement stdin, stdout et stderr, sinon ils devraient être fermés explicitement .
Plus d'informations ici: Formation de commandes de shell sanitaire ou d'appels système dans Ruby
gets
appels doivent passer l'argument nil
, sinon nous obtenons juste la première ligne de la sortie. Donc par exemple stdout.gets(nil)
.
Open3.popen3
manque un problème majeur: si vous avez un sous-processus qui écrit plus de données sur stdout qu'un tube ne peut en contenir, le sous-processus est suspendu stderr.write
et votre programme est bloqué stdout.gets(nil)
.
Juste pour mémoire, si vous voulez les deux (sortie et résultat de l'opération), vous pouvez faire:
output=`ls no_existing_file` ; result=$?.success?
output=`ls no_existing_file 2>&1`; result=$?.success?
La façon simple de le faire correctement et d'utiliser en toute sécurité Open3.capture2()
, Open3.capture2e()
ou Open3.capture3()
.
L'utilisation des astuces de ruby et de son %x
alias n'est PAS SÉCURISÉE SOUS AUCUNE CIRCONSTANCE 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 system
fonction, 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 qu'il renvoie le code de sortie au lieu de la sortie, et la capture de cette dernière est compliquée et désordonnée.
Jusqu'à présent, la meilleure réponse dans ce sujet mentionne Open3, mais pas les fonctions qui conviennent le mieux à la tâche. Open3.capture2
, capture2e
et capture3
fonctionne 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
Un autre mentionne IO.popen()
. La syntaxe peut être maladroite dans le sens où elle veut un tableau en entrée, mais ça marche aussi:
out = IO.popen(['echo', untrusted]).read # good
Pour plus de commodité, vous pouvez encapsuler Open3.capture3()
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')
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 devraient être fermés explicitement .
capture2
, capture2e
et capture3
fermez-les automatiquement std * s. (À tout le moins, je n'ai jamais rencontré de problème de ma part.)
Open3#popen2
, popen2e
et popen3
avec un bloc prédéfini: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/…
Vous pouvez utiliser system () ou% x [] selon le type de résultat dont vous avez besoin.
system () renvoyant true si la commande a été trouvée et exécutée avec succès, false dans le cas contraire.
>> 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 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
Le billet de blog de Jay Fields explique en détail les différences entre l'utilisation de system, exec et% x [..].
Si vous devez é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 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"
Vous utilisez des backticks:
`ls`
ruby -e '%x{ls}'
- notez, pas de sortie. (fyi %x{}
équivaut à des backticks.)
sh
ferait écho à la sortie vers la console (c'est-à-dire STDOUT) et la retournerait. Ce n'est pas le cas.
Une autre façon est:
f = open("|ls")
foo = f.read()
Notez que c'est le caractère "pipe" devant "ls" en open. Cela peut également être utilisé pour introduire des données dans l'entrée standard du programme ainsi que pour lire sa sortie standard.
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 j'ai utilisé ceci:
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 le formulaire de bloc fermera automatiquement stdin, stdout et stderr, sinon ils devraient être fermés explicitement .
Bien que l'utilisation de backticks ou de popen soit souvent ce que vous voulez vraiment, cela ne répond pas réellement à la question posée. Il peut y avoir des raisons valables pour capturer la system
sortie (peut-être pour les tests automatisés). Un petit googling a trouvé une réponse que je pensais publier 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' system
appel réel est enterré dans le code testé:
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é à l'aide d'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' system
appel par tout ce qui appelle en interne system
. Vous pouvez également utiliser une méthode similaire pour capturer stderr
si vous le souhaitez.
Si vous souhaitez que la sortie soit redirigée vers un fichier à l'aide 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 longue, cela stockera la sortie en temps réel. Vous pouvez également stocker la sortie à l'aide d'un IO.pipe et la rediriger depuis le système Kernel #.
En remplacement direct (...) du système, vous pouvez utiliser Open3.popen3 (...)
Discussion supplé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 la sortie complète.
Vous pouvez rediriger STDERR vers STDOUT si vous souhaitez capturer STDERR en utilisant le 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