Obtenir la sortie des appels system () dans Ruby


309

Si j'appelle une commande en utilisant le système Kernel # dans Ruby, comment puis-je obtenir sa sortie?

system("ls")

1
Vous voudrez peut-être jeter un œil à ce sujet dans comp.lang.ruby
Manrico Corazzi

Ceci est un fil très à la main, merci. La classe pour exécuter des commandes et obtenir des commentaires est excellente dans l'exemple de code.
illuminez

3
Pour les futurs googleurs. Si vous souhaitez en savoir plus sur les autres appels de commande système et leurs différences, consultez cette réponse SO .
Ouzbekjon

Réponses:


347

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

4
que faire si j'ai besoin de donner une variable dans le cadre de ma commande? C'est-à-dire, qu'est-ce que quelque chose comme système ("ls" + nom de fichier) se traduirait quand des backticks doivent être utilisés?
Vijay Dev

47
Vous pouvez faire l' évaluation de l' expression comme vous le feriez avec des chaînes régulières: ls #{filename}.
Craig Walker

36
Cette réponse n'est pas conseillée: elle introduit le nouveau problème de la saisie utilisateur non désinfectée.
Dogweather

4
@Dogweather: c'est peut-être vrai, mais est-ce différent des autres méthodes?
Craig Walker

20
si vous voulez capter stderr, mettez simplement 2> & 1 à la fin de votre commande. par exemple sortie =command 2>&1
micrée

243

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 systemet Open3.popen3fournir une variante sécurisée / d'échappement dans Ruby 1.8. Dans Ruby 1.9 IO::popenaccepte é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


26
C'est la seule réponse qui répond réellement à la question et résout le problème sans en introduire de nouvelles (entrée non-désinfectée).
Dogweather

2
Merci! C'est le genre de réponse que j'espérais. Une correction: les getsappels doivent passer l'argument nil, sinon nous obtenons juste la première ligne de la sortie. Donc par exemple stdout.gets(nil).
Greg Price

3
stdin, stdout et stderr doivent être fermés explicitement sous une forme non bloquante .
Yarin

Quelqu'un sait-il si quelque chose a changé dans Ruby 2.0 ou 2.1? Des modifications ou commentaires seraient appréciés ;-)
Simon Hürlimann

1
Je pense que la discussion autour Open3.popen3manque 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.writeet votre programme est bloqué stdout.gets(nil).
hagello

165

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?

4
Ceci est exactement ce que je cherchais. Je vous remercie.
jdl

12
Cela ne capture que stdout et stderr va à la console. Pour obtenir stderr, utilisez: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept

8
Cette réponse n'est pas sûre et ne doit pas être utilisée - si la commande est tout sauf une constante, la syntaxe de backtick est susceptible de provoquer un bogue, éventuellement une vulnérabilité de sécurité. (Et même si c'est une constante, cela entraînera probablement quelqu'un à l'utiliser pour une non-constante plus tard et provoquera un bogue.) Voir la réponse de Simon Hürlimann pour une solution correcte.
Greg Price

23
bravo à Greg Price pour sa compréhension de la nécessité d'échapper aux commentaires des utilisateurs, mais il n'est pas correct de dire que cette réponse telle qu'elle est écrite n'est pas sûre. La méthode Open3 mentionnée est plus compliquée et introduit plus de dépendances, et l'argument selon lequel quelqu'un "l'utilisera pour une non constante plus tard" est un homme de paille. Certes, vous ne les utiliseriez probablement pas dans une application Rails, mais pour un script utilitaire système simple sans possibilité d'entrée utilisateur non fiable, les backticks sont parfaitement corrects et personne ne devrait se sentir mal de les utiliser.
sbeam

69

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 %xalias 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 systemfonction, 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, capture2eet capture3fonctionne 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')

2
Ceci est la bonne réponse. C'est aussi le plus informatif. La seule chose qui manque est un avertissement concernant la fermeture des std * s. Voir cet autre commentaire : 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 .
Peter H. Boling

@ PeterH.Boling: Mieux, je le sais capture2, capture2eet capture3fermez-les automatiquement std * s. (À tout le moins, je n'ai jamais rencontré de problème de ma part.)
Denis de Bernardy

sans utiliser le formulaire de bloc, il n'y a aucun moyen pour une base de code de savoir quand quelque chose doit être fermé, donc je doute fortement qu'ils soient fermés. Vous n'avez probablement jamais rencontré de problème car ne pas les fermer ne causera pas de problèmes dans un processus de courte durée, et si vous redémarrez un processus de longue durée assez souvent, otto ne s'affichera pas non plus sauf si vous ouvrez std * s dans une boucle. Linux a une limite de descripteur de fichier élevée, que vous pouvez atteindre, mais jusqu'à ce que vous l'atteigniez, vous ne verrez pas le "bug".
Peter H. Boling,

2
@ PeterH.Boling: Non non, voir le code source. Les fonctions ne sont que des wrappers Open3#popen2, popen2eet popen3avec un bloc prédéfini: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/…
Denis de Bernardy

1
@Dennis de Barnardy Vous avez peut-être manqué que j'ai lié à la même documentation de classe (quoique pour Ruby 2.0.0 et une méthode différente. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… De l'exemple : `` `stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = wait_thr [: pid] # pid du processus démarré ... stdin.close # stdin , stdout et stderr doivent être fermés explicitement dans ce formulaire. stdout.close stderr.close `` `Je venais juste de citer la documentation." # stdin, stdout et stderr doivent être fermés explicitement dans ce formulaire. "
Peter H. Boling

61

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


2
Merci pour l'astuce d'utiliser% x []. Il vient de résoudre un problème que j'avais lorsque j'utilisais des ticks arrière dans un script ruby ​​sous Mac OS X. Lors de l'exécution du même script sur une machine Windows avec Cygwin, il a échoué à cause des ticks arrière, mais a fonctionné avec% x [].
Henrik Warne

22

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"

Merci! C'est parfait.
Greg Price

21

Vous utilisez des backticks:

`ls`

5
Les backticks ne produisent pas de sortie sur le terminal.
Mei

3
Il ne produit pas stderr mais il donne stdout.
Nickolay Kondratenko

1
Il n'écrit pas sur stdout ou stderr. Essayons cet exemple ruby -e '%x{ls}'- notez, pas de sortie. (fyi %x{}équivaut à des backticks.)
ocodo

Cela a très bien fonctionné. L'utilisation shferait écho à la sortie vers la console (c'est-à-dire STDOUT) et la retournerait. Ce n'est pas le cas.
Joshua Pinter

19

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.


Je viens de l'utiliser pour lire la sortie standard d'une commande aws cli afin de lire le json et non la valeur de retour officielle de 'true'
kraftydevil

14

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]

C'est une excellente solution.
Ronan Louarn


9

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 systemsortie (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' systemappel 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' systemappel par tout ce qui appelle en interne system. Vous pouvez également utiliser une méthode similaire pour capturer stderrsi vous le souhaitez.


8

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 utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.