用 OptionParser 构建 Command Line 工具

来源:互联网 发布:淘宝三只松鼠 编辑:程序博客网 时间:2024/05/22 06:14

用 OptionParser 构建 Command Line 工具

12 条评论 , 8 次修正,64 次阅读, 最后更新于 2 年前

Ruby 除了 Rails 还能做什么?

除了 Rails 之外,Ruby 能做的太多太多了,除了用于 Rails 开发之外,Ruby 用的最多的就是写各种 Command Line 工具来解决各种小问题,Command Line 工具又称为命令行工具。

提到用 Ruby 写命令行工具,就绕不过一个问题,如何解析命令行参数?

Unix 下的命令行工具

先啰嗦一下 Unix下的命令行工具,Unix 的命令行工具历史悠久,这里面故事非常非常多(以后再讲,或者参见 Unix编程艺术)。随着时间的推移,对于如何正确构建优良的命令行工具,Unix 社区慢慢形成了一整套完整的 Convertion 以及惯用法,如果你的命令行工具遵从这些 Convertion,那么用户将会非常容易的去使用你的命令行工具,甚至通过简洁的方式,将你的命令行工具和各种其他工具组合起来,用来完成各种复杂的操作。

正确的处理命令行参数对于写出高质量的命令行工具非常重要,那么如何正确的处理命令行参数呢?如果有 C 语言编程经验,或者用 C 语言写过命令行工具的人可能很熟悉 getopt(GNU getopt_long()),getopt 是 C Library 中一个专门用于解析命令行参数的工具,通常用 C 去写命令行工具的时候,getop 是一个很自然选择。

用 Ruby 写命令行工具

当使用 Ruby 写命令行工具的时候,我们在不借助任何内置/外置的命令行参数解析工具的情况下,可以直接从 ARGV 取到传入命令行的参数,然后手工判断,验证并执行后续操作。不过从遵循Unix的命令行工具的Convertion角度来讲,我不建议你直接从 ARGV 取数值,而是利用现有的库来作这件事情。Ruby 的标准库内置提供了一个 getopt 的 Ruby实现 GetoptLong,GetoptLong基本上模拟了 C 语言版本的全部接口/功能,不过 Ruby 开发社区不推荐你使用 GetoptLong,而是建议使用另外一个也是内置的且更加强大的解析库:OptionParser。

这个世界上总是有人不断的重新发明轮子,除了 Ruby 已经内置的 OptionParser,还有下面这些第三方实现的轮子:

  • Thor
  • Trollop
  • Gli
  • Choice
  • Optiflag

Thor 是 Rails 3 以后内建的命令行工具,严格意义上说,Thor 不仅仅用于解析命令行参数,而是用于替代 rake 作为新的 task 标准工具,Thor 的命令行参数解能是自己实现的,我个人建议在写 Rails 的 task 的时候,把 Thor 作为首选,但是作一般用途的命令行工具,Thor有点 overkill 了。

Gli 是一个用于建立“Git-Like Interface Command Line Parser”的工具,这里我简单给出一个什么是“Git-Like”的解释。通常 Unix 下的命令行工具都符合一个哲学,即“作一件事并且把它做好”,但是有些功能强大复杂的工具,如 Git,可以通过指定不同的 Action 执行不同的操作,比如 git 的 push 和 pull 操作:

$ git push
$ git pull

就是两个完全不同的操作,但是他们的command部分都是git,只是action部分不同。我们也可以把这样的通过不同的action来实现不同的操作的命令行工具叫做Command-Suit工具,即从功能上看,它不是一个命令,而是一个命令的suit集合。Gli就是帮助你快速实现这种Command-Suit的框架,如果你需要编写复杂的命令行工具,Gli是一个不错的选择。

TrollopChoiceOptiflag都是命令行参数的Ruby Parser,他们的目的一致,而且他们解析过程都遵循Unix的约定,只是实现各有不同,用法也不同,不过对我来说,他们都是一回事。就Unix命令行来说,参数只有Options,Arguments,以及Actions而已,所以具体用哪个,看你的个人喜好,简单对比下来我认为Choice的DSL语法最易读,简洁,优雅,如果你需要这些第三方Command Line parser的时候,不妨考虑一下Choice。不过我奉行另外一个原则,如果系统内置了的,我就不考虑第三方gem,而且Ruby内置的OptionParser足够强大,能满足我对解析Unix的命令行参数的一切需求,所以我优选使用OptionParser。这里我简单猜测一下为什么还有这么多第三方的轮子,第一是不知道Ruby已经内置了这个,第二个可能就是不爽Ruby内置的这个parser的文档或用法,虽然OptionParser足够强大灵活,但是不代表它好用,容易上手,相反,它的文档就相当坑爹!

用OptionParser创建命令行工具

下面这张图就是Ruby给出的OptionParser的文档,除了这张图片之外就是一个官方范例,然后就没了… 说实话我第一眼看了这张图和官方范例后感觉看不懂,需要反复通过Google各种文章和范例,才了解到了OptionParser的基本用法。

+--------------+| OptionParser |<>-----++--------------+       |                      +--------+                       |                    ,-| Switch |     on_head -------->+---------------+    /  +--------+     accept/reject -->| List          |<|>-                      |               |<|>-  +----------+     on ------------->+---------------+    `-| argument |                        :           :        |  class   |                      +---------------+      |==========|     on_tail -------->|               |      |pattern   |                      +---------------+      |----------|OptionParser.accept ->| DefaultList   |      |converter |             reject   |(shared between|      +----------+                      | all instances)|                      +---------------+

通常的Unix命令行参数包含下面这些形式:

  • Option - Option主要功能是用于调整命令行工具的行为,Option的表现通常有两种形式,short option或者long option。Option的类型有两种,switchflagswitch不带argument,而flag带有argument。
  • Argument - Argument通常表示命令行工具要操作的对象,通常是路径,URL或者名称等等。
  • Action - 表示命令行工具的行为,比如git命令的push或者pull等等。

举个例子git log --max-count=10git是command。log是action,表示查看git的提交历史。--max-count就是option,表示最多显示N条commit记录。而最后的=10就是argument,表示option的数值,即查看最后10条历史提交记录。所有的Unix命令行工具都遵循这样的一个约定,这里需要主意一下,Argument前面的=在很多命令行工具中是可以省略的。

用 OptionParser 创建一个简单的命令行工具,通常我们只需要创建一个OptionParser的实例 instance,然后给这个 instance 传入一个block,在这个 block 内部我们就可以使用 OptionParser 提供的方法来解析命令行参数,特别是用 on 方法来根据定义捕捉各种参数,并将参数解析成可被使用的 Ruby 数据,如 String,Boolean,Array 以及 Hash 等。而 on 方法最让人困惑的地方就是它异常灵活参数处理,比如on方法的第一个参数,如果是一个-加一个非空格字符,则把这个参数当作 switch 来处理,例如 on('-n'),如果是一个-开头的字符,后面跟着一个空格外加另外一个字符,那么就把这个参数当作flag处理,例如on('-n NAME')。如果on方法的参数超过两个,并且两个都是String,那么则视这两个参数表示一个意思,例如 on('-n NAME', '--name NAME')。如此这般的例子还有很多,如果有更高需求的朋友,我建议你还是直接去啃源代码吧。

下面我创建一个名为 my_awesome_command.rb 的命令行工具,这个工具直接输出我的命令行参数解析的结果,我用中文注释来说明OptionParser 是怎么用的:

#!/usr/bin/env rubyrequire 'optparse'options = {}option_parser = OptionParser.new do |opts|  # 这里是这个命令行工具的帮助信息  opts.banner = 'here is help messages of the command line tool.'  # Option 作为switch,不带argument,用于将 switch 设置成 true 或 false  options[:switch] = false  # 下面第一项是 Short option(没有可以直接在引号间留空),第二项是 Long option,第三项是对 Option 的描述  opts.on('-s', '--switch', 'Set options as switch') do    # 这个部分就是使用这个Option后执行的代码    options[:switch] = true  end  # Option 作为 flag,带argument,用于将argument作为数值解析,比如"name"信息  #下面的“value”就是用户使用时输入的argument  opts.on('-n NAME', '--name Name', 'Pass-in single name') do |value|    options[:name] = value  end  # Option 作为 flag,带一组用逗号分割的arguments,用于将arguments作为数组解析  opts.on('-a A,B', '--array A,B', Array, 'List of arguments') do |value|    options[:array] = value  endend.parse!puts options.inspect

执行结果

$ ruby my_awesome_command.rb -hhere is help messages of the command line tool.    -s, --switch                     Set options as switch    -n, --name Name                  Pass-in single name    -a, --array A,B                  List of arguments$ ruby my_awesome_command.rb -s{:switch=>true}$ ruby my_awesome_command.rb -n Daniel{:switch=>false, :name=>"Daniel"}$ ruby my_awesome_command.rb -a Foo,Bar{:switch=>false, :array=>["Foo", "Bar"]}

补充2点说明

  • 参数 opts.on('-s'和 opts.on('-s name'的区别是前者不用传参数
  • opts只是用于解析参数的,而不是执行,执行代码应在end.parse!之后,把options作为参数传入

希望以上内容能够帮助你掌握写出符合Unix标准的命令行参数的工具,如果要写出易用,对用户友好,跟其他命令行工具互动良好,可测试,可维护,可格式化输出内容的真正awesome的命令行工具,您仍然需要继续努力,加油吧!

0 0
原创粉丝点击