首页 > 解决方案 > 带有自定义命令的 Ruby 自定义 shell

问题描述

我想编写一个 Ruby 程序,它基本上是一个带有自定义命令的 shell。这意味着在调用“ ruby app.rb ”之后,用户将进入一个支持我编写的特殊命令的 shell。

我曾经loop do ... end创建一个处理用户输入的基本循环。我还使用了一个字符串数组来存储所有可用的命令,以及一个包含每个可用命令的帮助消息的哈希表。但这非常麻烦。

我想知道如何优雅地定义带有标志的自定义命令,其中一些是强制性的(例如 create -f "~/file.txt" --READONLY -v)。不仅如此,我还需要我的 shell 的“帮助”命令来输出关于每个自定义命令的信息,包括标志,并有一个列出每个可用命令的“菜单”命令。作为最后的手段,我想简单地为每个命令创建一堆 .rb 文件,然后使用 OptionParser 作为标志和参数,因为我不知道如何让 OptionParser 使用函数而不是文件。

到目前为止,这是我想出的:

class Shell
    @@input = ""

    # List of all commands
    @@commands = [
        "help",
        "exit"
    ]

    # Help messages associated with commands
    @@help_strings = Hash[
        "help" => "Outputs this info.",
        "exit" => "Closes the program."
    ]


    def self.start
        # bash clear screen command
        system("clear")

        loop do
             print (">> ")
             input = gets.chomp

             case input
             when "help"
                 for com in @@commands
                     puts("#{com}: #{@@help_strings[com]}")
                 end
             when "exit"
                 # bash clear screen command
                 system("clear")
                 break
             else
                 puts("Unknown commands: #{input}")
                 puts("Type \"help\" for more info.")
             end
        end
    end
end

标签: ruby

解决方案


创建一个健壮的REPL有很多工作要做,但是您可以采取一些优化措施来简化您在此处获得的内容。

  1. 创建一个Runner包含所有命令的单独类。使用methodsandinstance_methods获取支持的命令列表,而无需维护单独的列表。
  2. 检查是否支持命令,然后直接调用方法而不是使用case语句。
  3. 捕获ctrl+c以干净地退出循环。
class Shell
  class Runner
    def supported_method?(method)
      # This will return an array of the instance methods that are not provided by ancestors
      (methods - Object.instance_methods).include?(method.to_sym)
    end

    def help
      puts "[TODO: Help info]"
    end

    def exit
      system "clear"
    end
  end

  def self.start
    runner = Runner.new
    loop do 
      print (">> ")
      input = gets.chomp
      if runner.supported_method?(input)
        runner.send(input)
      else 
        puts("Unknown commands: #{input}\nType \"help\" for more info.")
      end
    end
  rescue SystemExit, Interrupt, IRB::Abort
    puts "\nShutting down."
  end
end

推荐阅读