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")
288
demandé sur Per Lundberg 2009-03-27 18:12:54

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
325
répondu Craig Walker 2017-05-23 11:47:30

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

224
répondu Simon Hürlimann 2017-05-23 12:10:44

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?
160
répondu FernandoFabreti 2011-12-01 15:25:10

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[..] .

58
répondu Martin Gross 2010-11-25 16:04:59

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')
57
répondu Denis de Bernardy 2016-04-06 13:49:44

vous utilisez des bâtons arrière:

`ls`
19
répondu chaos 2009-03-27 15:17:37

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.

19
répondu dwc 2009-03-27 15:23:20

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"
19
répondu user495470 2013-02-20 04:27:09

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]
13
répondu Geoff van der Meer 2010-03-16 05:43:40

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 .

10
répondu Yarin 2017-05-23 12:18:27

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.

8
répondu Ashrith 2013-05-26 04:58:41

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.

7
répondu Eric Anderson 2013-05-16 22:47:47

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

6
répondu Antonin Hildebrand 2010-03-04 14:24:58

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

0
répondu dac2009 2017-02-27 15:39:15
puts `date`
puts $?


Mon Mar  7 19:01:15 PST 2016
pid 13093 exit 0
-1
répondu Ron Hinchley 2016-03-08 03:02:26