359 votes

Obtenir la sortie des appels system() en Ruby

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

system("ls")

1 votes

Vous voudrez peut-être jeter un coup d'œil à ce fil dans comp.lang.ruby

0 votes

C'est un fil très manuel, merci. La classe permettant d'exécuter des commandes et d'obtenir un retour d'information est excellente dans l'exemple de code.

5 votes

Pour les futurs googleurs. Si vous voulez en savoir plus sur les autres appels de commandes système et leurs différences, voir cette réponse SO .

391voto

Craig Walker Points 13478

J'aimerais développer et clarifier réponse du chaos un peu.

Si vous entourez votre commande de points de suspension, 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 alors affecter la valeur à une variable comme suit :

output = `ls`
p output

ou

printf output # escapes newline chars

4 votes

Que faire si je dois donner une variable dans le cadre de ma commande ? En d'autres termes, comment se traduirait quelque chose comme system("ls " + nom du fichier) si l'on devait utiliser des backticks ?

47 votes

Vous pouvez effectuer l'évaluation des expressions comme vous le feriez avec des chaînes régulières : ls #{filename} .

1 votes

Hrm, makdown a mangé ma mise en page. Il y a des tics arrière devant le "l" de "ls" et après le "}" de "#{filename}".

265voto

Simon Hürlimann Points 1533

Sachez que toutes les solutions où vous passez une chaîne de caractères contenant des valeurs fournies par l'utilisateur à l'adresse system , %x[] etc. ne sont pas sûrs ! Unsafe signifie en fait : l'utilisateur peut déclencher l'exécution du code dans le contexte et avec toutes les permissions du programme.

Pour autant que je puisse dire, seulement system y Open3.popen3 fournissent une variante sécurisée/escaping dans Ruby 1.8. En Ruby 1.9 IO::popen accepte également un tableau.

Il suffit de passer chaque option et argument sous forme de tableau à l'un de ces appels.

Si vous avez besoin non seulement de l'état de sortie mais aussi du résultat, vous voudrez 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, il faudrait que ce soit fermé explicitement .

Plus d'informations ici : Former des commandes shell ou des appels système sanitaires en Ruby

27 votes

C'est la seule réponse qui réponde réellement à la question et qui résout le problème sans en introduire de nouveaux (intrants non assainis).

2 votes

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

3 votes

Stdin, stdout et stderr doivent être fermé explicitement sous forme non-bloquée .

181voto

FernandoFabreti Points 1087

Pour mémoire, si vous voulez les deux (sortie et résultat de l'opération), vous pouvez le faire :

output=`ls no_existing_file` ;  result=$?.success?

4 votes

C'est exactement ce que je recherchais. Merci.

13 votes

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

10 votes

Cette réponse est non sécurisé et ne devrait pas être utilisé -- si la commande est autre chose qu'une constante, alors la syntaxe backtick est susceptible de provoquer un bogue, voire une faille de sécurité. (Et même s'il s'agit d'une constante, quelqu'un l'utilisera probablement pour une non-constante plus tard et provoquera un bogue). Voir La réponse de Simon Hürlimann pour une solution correcte.

91voto

Denis Points 34131

La façon la plus simple de le faire correctement et en toute sécurité est d'utiliser la fonction Open3.capture2() , Open3.capture2e() ou Open3.capture3() .

Utiliser les backticks de ruby et ses %x Les alias sont NON SÉCURISÉ EN TOUTES CIRCONSTANCES si elle est utilisée avec des données non fiables. Il est DANGEREUX tout simplement :

untrusted = "; date; echo"
out = `echo #{untrusted}`                              # BAD

untrusted = '"; date; echo"'
out = `echo "#{untrusted}"`                            # BAD

untrusted = "'; date; echo'"
out = `echo '#{untrusted}'`                            # BAD

El system En revanche, les arguments sont échappés correctement si elle est utilisée correctement :

ret = system "echo #{untrusted}"                       # BAD
ret = system 'echo', untrusted                         # good

Le problème est qu'il renvoie le code de sortie au lieu de la sortie, et que la capture de cette dernière est compliquée et désordonnée.

La meilleure réponse dans ce fil jusqu'à présent mentionne Open3, mais pas les fonctions qui sont les mieux adaptées à cette tâche. Open3.capture2 , capture2e y capture3 travailler 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 elle fonctionne aussi :

out = IO.popen(['echo', untrusted]).read               # good

Pour plus de commodité, vous pouvez emballer 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

Ejemplo:

p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')

Donne les résultats suivants :

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 votes

C'est la bonne réponse. C'est aussi la plus informative. 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, il faudrait que ce soit fermé explicitement .

0 votes

@PeterH.Boling : Autant que je sache, le capture2 , capture2e y capture3 ferme également les std*s automatiquement. (En tout cas, je n'ai jamais rencontré le problème de mon côté).

0 votes

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 hautement Je doute qu'ils soient fermés. Vous n'avez probablement jamais rencontré de problème parce que le fait de 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 n'y apparaîtra pas non plus, à moins que vous n'ouvriez des std*s en boucle. Linux a une limite élevée pour les descripteurs de fichiers, que vous pouvez atteindre, mais tant que vous ne l'aurez pas atteinte, vous ne verrez pas le "bug".

61voto

Martin Gross Points 663

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 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[ ], quant à lui, enregistre les résultats de la commande sous forme de 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 article de blog par Jay Fields explique en détail les différences entre l'utilisation de system, exec et %x[ ] .

2 votes

Merci pour l'astuce d'utiliser %x[]. Cela vient de résoudre un problème que j'avais où j'utilisais des ticks arrière dans un script ruby sous Mac OS X. Lorsque j'ai exécuté le même script sur une machine Windows avec Cygwin, il a échoué à cause des back ticks, mais a fonctionné avec %x[].

0 votes

Merci beaucoup ! %x[..] c'est la solution compacte et fonctionnelle !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X