Appel de commandes shell depuis Ruby
Question
Comment appeler des commandes shell depuis un programme Ruby ?Comment puis-je ensuite récupérer le résultat de ces commandes dans Ruby ?
La solution
Cette explication est basée sur un commentaire Script Ruby d'un de mes amis.Si vous souhaitez améliorer le script, n'hésitez pas à le mettre à jour via le lien.
Tout d’abord, notez que lorsque Ruby appelle un shell, il appelle généralement /bin/sh
, pas Frapper.Certaines syntaxes Bash ne sont pas prises en charge par /bin/sh
sur tous les systèmes.
Voici différentes manières d'exécuter un script shell :
cmd = "echo 'hi'" # Sample string that can be used
Kernel#`
, communément appelés backticks –`cmd`
C'est comme beaucoup d'autres langages, notamment Bash, PHP et Perl.
Renvoie le résultat de la commande shell.
Documents : http://ruby-doc.org/core/Kernel.html#method-i-60
value = `echo 'hi'` value = `#{cmd}`
Syntaxe intégrée,
%x( cmd )
Suivant le
x
caractère est un délimiteur, qui peut être n'importe quel caractère.Si le délimiteur est l'un des caractères(
,[
,{
, ou<
, Le littéral se compose des personnages à la clôture de clôture correspondante, en tenant compte des paires de délimitres imbriquées.Pour tous les autres délimiteurs, le littéral comprend les caractères jusqu'à la prochaine occurrence du caractère délimiteur.Interpolation de chaîne#{ ... }
est autorisée.Renvoie le résultat de la commande shell, tout comme les backticks.
Documents : http://www.ruby-doc.org/docs/ProgrammingRuby/html/langue.html
value = %x( echo 'hi' ) value = %x[ #{cmd} ]
Kernel#system
Exécute la commande donnée dans un sous-shell.
Retour
true
si la commande a été trouvée et exécutée avec succès,false
sinon.Documents : http://ruby-doc.org/core/Kernel.html#method-i-system
wasGood = system( "echo 'hi'" ) wasGood = system( cmd )
Kernel#exec
Remplace le processus actuel en exécutant la commande externe donnée.
Ne renvoie aucun, le processus en cours est remplacé et ne continue jamais.
Documents : http://ruby-doc.org/core/Kernel.html#method-i-exec
exec( "echo 'hi'" ) exec( cmd ) # Note: this will never be reached because of the line above
Voici quelques conseils supplémentaires :$?
, ce qui équivaut à $CHILD_STATUS
, accède à l'état de la dernière commande système exécutée si vous utilisez les backticks, system()
ou %x{}
.Vous pourrez alors accéder au exitstatus
et pid
propriétés:
$?.exitstatus
Pour en savoir plus, voir :
Autres conseils
Voici un organigramme basé sur cette réponse.Voir également, en utilisant script
pour émuler un terminal.
La façon dont j'aime faire cela est d'utiliser le %x
littéral, ce qui rend facile (et lisible !) l'utilisation de guillemets dans une commande, comme ceci :
directorylist = %x[find . -name '*test.rb' | sort]
Ce qui, dans ce cas, remplira la liste des fichiers avec tous les fichiers de test du répertoire actuel, que vous pourrez traiter comme prévu :
directorylist.each do |filename|
filename.chomp!
# work with file
end
Voici le meilleur article à mon avis sur l'exécution de scripts shell dans Ruby :"6 façons d'exécuter des commandes Shell dans Ruby".
Si vous avez seulement besoin d'obtenir la sortie, utilisez des backticks.
J'avais besoin de trucs plus avancés comme STDOUT et STDERR, j'ai donc utilisé la gemme Open4.Vous y avez toutes les méthodes expliquées.
Mon préféré est Ouvert3
require "open3"
Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
Voici quelques éléments à prendre en compte lors du choix entre ces mécanismes :
- Voulez-vous simplement STDOUT ou avez-vous également besoin de stderr?ou même séparé?
- Quelle est la taille de votre production ?Voulez-vous maintenir le résultat entier dans la mémoire?
- Voulez-vous lire une partie de votre sortie alors que le sous-processus est toujours en cours d'exécution?
- Avez-vous besoin de codes de résultat ?
- Avez-vous besoin d'un objet Ruby qui représente le processus et vous permet de le tuer à la demande?
Vous aurez peut-être besoin de n'importe quoi, des simples backticks (``), system() et IO.popen
à part entière Kernel.fork
/Kernel.exec
avec IO.pipe
et IO.select
.
Vous souhaiterez peut-être également ajouter des délais d'attente au mix si un sous-processus prend trop de temps à s'exécuter.
Malheureusement, c'est beaucoup dépend.
Une option supplémentaire :
Lorsque vous:
- besoin de stderr ainsi que de stdout
- je ne peux pas/ne veux pas utiliser Open3/Open4 (ils lancent des exceptions dans NetBeans sur mon Mac, je ne sais pas pourquoi)
Vous pouvez utiliser la redirection shell :
puts %x[cat bogus.txt].inspect
=> ""
puts %x[cat bogus.txt 2>&1].inspect
=> "cat: bogus.txt: No such file or directory\n"
Le 2>&1
la syntaxe fonctionne partout Linux, Mac et les fenêtres depuis les débuts de MS-DOS.
Je ne suis certainement pas un expert Ruby, mais je vais essayer :
$ irb
system "echo Hi"
Hi
=> true
Vous devriez également être capable de faire des choses comme :
cmd = 'ls'
system(cmd)
Les réponses ci-dessus sont déjà très intéressantes, mais j'ai vraiment envie de partager l'article récapitulatif suivant :"6 façons d'exécuter des commandes Shell dans Ruby"
En gros, cela nous dit :
Kernel#exec
:
exec 'echo "hello $HOSTNAME"'
system
et $?
:
system 'false'
puts $?
Backticks (`):
today = `date`
IO#popen
:
IO.popen("date") { |f| puts f.gets }
Open3#popen3
--stdlib :
require "open3"
stdin, stdout, stderr = Open3.popen3('dc')
Open4#popen4
-- une gemme:
require "open4"
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]
Si vous avez vraiment besoin de Bash, selon la note dans la "meilleure" réponse.
Tout d’abord, notez que lorsque Ruby appelle un shell, il appelle généralement
/bin/sh
, pas Frapper.Certaines syntaxes Bash ne sont pas prises en charge par/bin/sh
sur tous les systèmes.
Si vous devez utiliser Bash, insérez bash -c "your Bash-only command"
à l'intérieur de la méthode d'appel souhaitée.
quick_output = system("ls -la")
quick_bash = system("bash -c 'ls -la'")
Tester:
system("echo $SHELL")
system('bash -c "echo $SHELL"')
Ou si vous exécutez un fichier de script existant (par exemple script_output = system("./my_script.sh")
) Rubis devrait Honore le shebang, mais tu peux toujours utiliser system("bash ./my_script.sh")
pour être sûr (bien qu'il puisse y avoir un léger surcoût dû à /bin/sh
en cours d'exécution /bin/bash
, vous ne le remarquerez probablement pas.
Vous pouvez également utiliser les opérateurs backtick (`), similaires à Perl :
directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory
Pratique si vous avez besoin de quelque chose de simple.
La méthode que vous souhaitez utiliser dépend exactement de ce que vous essayez d'accomplir ;consultez la documentation pour plus de détails sur les différentes méthodes.
Le moyen le plus simple est par exemple :
reboot = `init 6`
puts reboot
En utilisant les réponses ici et liées dans la réponse de Mihai, j'ai mis en place une fonction qui répond à ces exigences :
- Capture parfaitement STDOUT et STDERR afin qu'ils ne "fuient" pas lorsque mon script est exécuté depuis la console.
- Permet de transmettre les arguments au shell sous forme de tableau, vous n'avez donc pas à vous soucier de l'échappement.
- Capture l'état de sortie de la commande afin qu'il soit clair lorsqu'une erreur s'est produite.
En prime, celui-ci renverra également STDOUT dans les cas où la commande shell se termine avec succès (0) et met quoi que ce soit sur STDOUT.En cela, il diffère de system
, ce qui renvoie simplement true
dans ces cas.
Le code suit.La fonction spécifique est system_quietly
:
require 'open3'
class ShellError < StandardError; end
#actual function:
def system_quietly(*cmd)
exit_status=nil
err=nil
out=nil
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thread|
err = stderr.gets(nil)
out = stdout.gets(nil)
[stdin, stdout, stderr].each{|stream| stream.send('close')}
exit_status = wait_thread.value
end
if exit_status.to_i > 0
err = err.chomp if err
raise ShellError, err
elsif out
return out.chomp
else
return true
end
end
#calling it:
begin
puts system_quietly('which', 'ruby')
rescue ShellError
abort "Looks like you don't have the `ruby` command. Odd."
end
#output: => "/Users/me/.rvm/rubies/ruby-1.9.2-p136/bin/ruby"
Nous pouvons y parvenir de plusieurs manières.
En utilisant Kernel#exec
, rien après l'exécution de cette commande :
exec('ls ~')
En utilisant backticks or %x
`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"
En utilisant Kernel#system
commande, renvoie true
en cas de succès, false
en cas d'échec et revient nil
si l'exécution de la commande échoue :
system('ls ~')
=> true
N'oubliez pas le spawn
commande pour créer un processus en arrière-plan pour exécuter la commande spécifiée.Vous pouvez même attendre qu'il soit terminé en utilisant le Process
classe et le retour pid
:
pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
Process.wait pid
pid = spawn(RbConfig.ruby, "-eputs'Hello, world!'")
Process.wait pid
Le doc dit :Cette méthode est similaire à #system
mais il n'attend pas la fin de la commande.
Si vous avez un cas plus complexe que le cas courant (qui ne peut être traité avec ``
) puis consultez Kernel.spawn()
ici.Cela semble être le plus générique/le plus complet proposé par stock Rubis pour exécuter des commandes externes.
Par exemple.vous pouvez l'utiliser pour :
- créer des groupes de processus (Windows)
- rediriger l'entrée, la sortie, l'erreur vers les fichiers/les uns les autres.
- définir les variables d'environnement, umask
- changer le répertoire avant d'exécuter la commande
- définir des limites de ressources pour le processeur/les données/...
- Faites tout ce qui peut être fait avec d'autres options dans d'autres réponses, mais avec plus de code.
Officiel documentation rubis a des exemples assez bons.
env: hash
name => val : set the environment variable
name => nil : unset the environment variable
command...:
commandline : command line string which is passed to the standard shell
cmdname, arg1, ... : command name and one or more arguments (no shell)
[cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
options: hash
clearing environment variables:
:unsetenv_others => true : clear environment variables except specified by env
:unsetenv_others => false : dont clear (default)
process group:
:pgroup => true or 0 : make a new process group
:pgroup => pgid : join to specified process group
:pgroup => nil : dont change the process group (default)
create new process group: Windows only
:new_pgroup => true : the new process is the root process of a new process group
:new_pgroup => false : dont create a new process group (default)
resource limit: resourcename is core, cpu, data, etc. See Process.setrlimit.
:rlimit_resourcename => limit
:rlimit_resourcename => [cur_limit, max_limit]
current directory:
:chdir => str
umask:
:umask => int
redirection:
key:
FD : single file descriptor in child process
[FD, FD, ...] : multiple file descriptor in child process
value:
FD : redirect to the file descriptor in parent process
string : redirect to file with open(string, "r" or "w")
[string] : redirect to file with open(string, File::RDONLY)
[string, open_mode] : redirect to file with open(string, open_mode, 0644)
[string, open_mode, perm] : redirect to file with open(string, open_mode, perm)
[:child, FD] : redirect to the redirected file descriptor
:close : close the file descriptor in child process
FD is one of follows
:in : the file descriptor 0 which is the standard input
:out : the file descriptor 1 which is the standard output
:err : the file descriptor 2 which is the standard error
integer : the file descriptor of specified the integer
io : the file descriptor specified as io.fileno
file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not
:close_others => false : inherit fds (default for system and exec)
:close_others => true : dont inherit (default for spawn and IO.popen)
La méthode backticks `est la plus simple pour appeler des commandes shell à partir de Ruby.Il renvoie le résultat de la commande shell.
url_request = 'http://google.com' result_of_shell_command = `curl #{url_request}`
Étant donné une commande, par exemple attrib
require 'open3'
a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
puts stdout.read
end
J'ai découvert que même si cette méthode n'est pas aussi mémorable que par ex.system("thecommand") ou thecommand dans les backticks, une bonne chose à propos de cette méthode par rapport aux autres méthodes.est par ex.backticks ne semble pas me permettre de « mettre » la commande que j'exécute/stocker la commande que je veux exécuter dans une variable, et system("thecommand") ne semble pas me permettre d'obtenir le résultat.Alors que cette méthode me permet de faire ces deux choses et d’accéder indépendamment à stdin, stdout et stderr.
https://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
http://ruby-doc.org/stdlib-2.4.1/libdoc/open3/rdoc/Open3.html
Pas vraiment une réponse, mais peut-être que quelqu'un trouvera cela utile, et cela concerne cela.
Lorsque vous utilisez l'interface graphique TK sous Windows et que vous devez appeler des commandes shell depuis Rubyw, vous aurez toujours une fenêtre cmd ennuyeuse apparaissant pendant moins d'une seconde.
Pour éviter cela, vous pouvez utiliser
WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0)
ou
WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0)
Les deux stockeront la sortie d'ipconfig dans 'log.txt', mais aucune fenêtre ne s'affichera.
Tu devras require 'win32ole'
à l'intérieur de votre script.
system()
, exec()
et spawn()
fera apparaître cette fenêtre ennuyeuse lors de l'utilisation de TK et Rubyw.
En voici un sympa que j'utilise dans un script Ruby sous OS X (afin que je puisse démarrer un script et obtenir une mise à jour même après avoir quitté la fenêtre) :
cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )