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 ?

Était-ce utile?

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
  1. 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}`
    
  2. 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} ]
    
  3. 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 )
    
  4. 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.

enter image description here

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 :

  1. Voulez-vous simplement STDOUT ou avez-vous également besoin de stderr?ou même séparé?
  2. Quelle est la taille de votre production ?Voulez-vous maintenir le résultat entier dans la mémoire?
  3. Voulez-vous lire une partie de votre sortie alors que le sous-processus est toujours en cours d'exécution?
  4. Avez-vous besoin de codes de résultat ?
  5. 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 :

  1. Capture parfaitement STDOUT et STDERR afin qu'ils ne "fuient" pas lorsque mon script est exécuté depuis la console.
  2. Permet de transmettre les arguments au shell sous forme de tableau, vous n'avez donc pas à vous soucier de l'échappement.
  3. 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 )
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top