【问题标题】:How do you specify a required switch (not argument) with Ruby OptionParser?如何使用 Ruby OptionParser 指定所需的开关(不是参数)?
【发布时间】:2026-02-24 11:45:02
【问题描述】:

我正在编写一个脚本,我想要一个带有值的--host 开关,但是如果没有指定--host 开关,我希望选项解析失败。

我似乎不知道该怎么做。文档似乎只指定了如何使参数值强制,而不是开关本身。

【问题讨论】:

  • 你需要详细说明,举个例子......

标签: ruby arguments optionparser


【解决方案1】:

这个想法是定义一个OptionParser,然后定义parse!,如果缺少某些字段,则定义puts。默认情况下将filename 设置为空字符串可能不是最好的方法,但你明白了。

require 'optparse'

filename = ''
options = OptionParser.new do |opts|
    opts.banner = "Usage: swift-code-style.rb [options]"

    opts.on("-iNAME", "--input-filename=NAME", "Input filename") do |name|
        filename = name
    end
    opts.on("-h", "--help", "Prints this help") do
        puts opts
        exit
    end
end

options.parse!

if filename == ''
    puts "Missing filename.\n---\n"
    puts options
    exit
end

puts "Processing '#{filename}'..."

如果-i filename 缺失,则显示:

~/prj/gem/swift-code-kit ./swift-code-style.rb
Missing filename.
---
Usage: swift-code-style.rb [options]
    -i, --input-filename=NAME        Input filename
    -h, --help                       Prints this help

【讨论】:

    【解决方案2】:

    我想出了一个清晰简洁的解决方案来总结您的贡献。它会引发OptionParser::MissingArgument 异常,其中缺少参数作为消息。此异常与来自 OptionParser 的其余异常一起被捕获在 rescue 块中。

    #!/usr/bin/env ruby
    require 'optparse'
    
    options = {}
    
    optparse = OptionParser.new do |opts|
      opts.on('-h', '--host hostname', "Host name") do |host|
        options[:host] = host
      end
    end
    
    begin
      optparse.parse!
      mandatory = [:host]
      missing = mandatory.select{ |param| options[param].nil? }
      raise OptionParser::MissingArgument, missing.join(', ') unless missing.empty?
    rescue OptionParser::ParseError => e
      puts e
      puts optparse
      exit
    end
    

    运行这个例子:

     ./program            
    missing argument: host
    Usage: program [options]
        -h, --host hostname              Host name
    

    【讨论】:

      【解决方案3】:

      一种使用 optparse 的方法,在丢失的开关上提供友好的输出:

      #!/usr/bin/env ruby
      require 'optparse'
      
      options = {}
      
      optparse = OptionParser.new do |opts|
        opts.on('-f', '--from SENDER', 'username of sender') do |sender|
          options[:from] = sender
        end
      
        opts.on('-t', '--to RECIPIENTS', 'comma separated list of recipients') do |recipients|
          options[:to] = recipients
        end
      
        options[:number_of_files] = 1
        opts.on('-n', '--num_files NUMBER', Integer, "number of files to send (default #{options[:number_of_files]})") do |number_of_files|
          options[:number_of_files] = number_of_files
        end
      
        opts.on('-h', '--help', 'Display this screen') do
          puts opts
          exit
        end
      end
      
      begin
        optparse.parse!
        mandatory = [:from, :to]                                         # Enforce the presence of
        missing = mandatory.select{ |param| options[param].nil? }        # the -t and -f switches
        unless missing.empty?                                            #
          raise OptionParser::MissingArgument.new(missing.join(', '))    #
        end                                                              #
      rescue OptionParser::InvalidOption, OptionParser::MissingArgument      #
        puts $!.to_s                                                           # Friendly output when parsing fails
        puts optparse                                                          #
        exit                                                                   #
      end                                                                      #
      
      puts "Performing task with options: #{options.inspect}"
      

      在没有-t-f 开关的情况下运行会显示以下输出:

      Missing options: from, to
      Usage: test_script [options]
          -f, --from SENDER                username of sender
          -t, --to RECIPIENTS              comma separated list of recipients
          -n, --num_files NUMBER           number of files to send (default 1)
          -h, --help
      

      在 begin/rescue 子句中运行 parse 方法允许对其他故障进行友好格式化,例如缺少参数或无效的开关值,例如,尝试为 -n 开关传递字符串。

      【讨论】:

      • 根据 neilfws 的 cmets 修复
      • 这还不错,但它仍然不是很干燥。最后你必须做很多工作,并且必须在两个地方指定你的开关。在下面查看我的修复,它更简单,更干燥。也在我的博客上:picklepumpers.com/wordpress/?p=949
      • 在 STDERR 上输出可能会更好。
      【解决方案4】:

      如果你这样做:

      opts.on('-h', '--host',
                'required host name [STRING]') do |h|
          someoptions[:host] = h || nil
        end
      

      然后someoptions[:host] 将是来自命令行的值或nil(如果您不提供 --host 和/或在 --host 之后没有值)并且您可以轻松地对其进行测试(并且有条件失败)解析后:

      fail "Hostname not provided" unless someoptions[:host]
      

      【讨论】:

        【解决方案5】:

        来自未知(谷歌)的答案很好,但包含一个小错误。

        rescue OptionParser::InvalidArgument, OptionParser::MissingArgument
        

        应该是

        OptionParser::InvalidOption, OptionParser::MissingArgument
        

        否则,optparse.parse! 将触发OptionParser::InvalidOption 的标准错误输出,而不是自定义消息。

        【讨论】:

          【解决方案6】:

          如果需要host,那么肯定不是选项,而是参数

          考虑到这一点,这里有一种方法可以解决您的问题。您可以查询ARGV 数组以查看是否已指定主机,如果尚未指定,则调用abort("You must specify a host!") 或类似方法,以使您的程序以错误状态退出。

          【讨论】:

            【解决方案7】:

            我把它变成了一个 gem,你可以从 ruby​​gems.org 下载和安装:

            gem install pickled_optparse
            

            您可以在 github 上查看更新的项目源代码:
            http://github.com/PicklePumpers/pickled_optparse

            -- 较早的帖子信息--

            这真的非常困扰我,所以我修复了它保持使用超级干燥。

            要使开关成为必需,只需在选项数组的任意位置添加一个 :required 符号,如下所示:

            opts.on("-f", "--foo [Bar]", String, :required, "Some required option") do |option|
              @options[:foo] = option
            end
            

            然后在您的 OptionParser 块的末尾添加其中一个以打印出缺少的开关和使用说明:

            if opts.missing_switches?
              puts opts.missing_switches
              puts opts
              exit
            end
            

            最后,为了使这一切正常工作,您需要将以下“optparse_required_switches.rb”文件添加到您的项目中的某个地方,并在您进行命令行解析时需要它。

            我在博客上写了一篇带有示例的小文章: http://picklepumpers.com/wordpress/?p=949

            这是修改后的 OptionParser 文件及其用法示例:

            required_switches_example.rb

            #!/usr/bin/env ruby
            require 'optparse'
            require_relative 'optparse_required_switches'
            
            # Configure options based on command line options
            @options = {}
            OptionParser.new do |opts|
              opts.banner = "Usage: test [options] in_file[.srt] out_file[.srt]"
            
              # Note that :required can be anywhere in the parameters
            
              # Also note that OptionParser is bugged and will only check 
              # for required parameters on the last option, not my bug.
            
              # required switch, required parameter
              opts.on("-s Short", String, :required, "a required switch with just a short") do |operation|
                @options[:operation] = operation
              end
            
              # required switch, optional parameter
              opts.on(:required, "--long [Long]", String, "a required switch with just a long") do |operation|
                @options[:operation] = operation
              end
            
              # required switch, required parameter
              opts.on("-b", "--both ShortAndLong", String, "a required switch with short and long", :required) do |operation|
                @options[:operation] = operation
              end
            
              # optional switch, optional parameter
              opts.on("-o", "--optional [Whatever]", String, "an optional switch with short and long") do |operation|
                @options[:operation] = operation
              end
            
              # Now we can see if there are any missing required 
              # switches so we can alert the user to what they 
              # missed and how to use the program properly.
              if opts.missing_switches?
                puts opts.missing_switches
                puts opts
                exit
              end
            
            end.parse!
            

            optparse_required_switches.rb

            # Add required switches to OptionParser
            class OptionParser
            
              # An array of messages describing the missing required switches
              attr_reader :missing_switches
            
              # Convenience method to test if we're missing any required switches
              def missing_switches?
                !@missing_switches.nil?
              end
            
              def make_switch(opts, block = nil)
                short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], []
                ldesc, sdesc, desc, arg = [], [], []
                default_style = Switch::NoArgument
                default_pattern = nil
                klass = nil
                n, q, a = nil
            
                # Check for required switches
                required = opts.delete(:required)
            
                opts.each do |o|
            
                  # argument class
                  next if search(:atype, o) do |pat, c|
                    klass = notwice(o, klass, 'type')
                    if not_style and not_style != Switch::NoArgument
                      not_pattern, not_conv = pat, c
                    else
                      default_pattern, conv = pat, c
                    end
                  end
            
                  # directly specified pattern(any object possible to match)
                  if (!(String === o || Symbol === o)) and o.respond_to?(:match)
                    pattern = notwice(o, pattern, 'pattern')
                    if pattern.respond_to?(:convert)
                      conv = pattern.method(:convert).to_proc
                    else
                      conv = SPLAT_PROC
                    end
                    next
                  end
            
                  # anything others
                  case o
                    when Proc, Method
                      block = notwice(o, block, 'block')
                    when Array, Hash
                      case pattern
                        when CompletingHash
                        when nil
                          pattern = CompletingHash.new
                          conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert)
                        else
                          raise ArgumentError, "argument pattern given twice"
                      end
                      o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}}
                    when Module
                      raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4))
                    when *ArgumentStyle.keys
                      style = notwice(ArgumentStyle[o], style, 'style')
                    when /^--no-([^\[\]=\s]*)(.+)?/
                      q, a = $1, $2
                      o = notwice(a ? Object : TrueClass, klass, 'type')
                      not_pattern, not_conv = search(:atype, o) unless not_style
                      not_style = (not_style || default_style).guess(arg = a) if a
                      default_style = Switch::NoArgument
                      default_pattern, conv = search(:atype, FalseClass) unless default_pattern
                      ldesc << "--no-#{q}"
                      long << 'no-' + (q = q.downcase)
                      nolong << q
                    when /^--\[no-\]([^\[\]=\s]*)(.+)?/
                      q, a = $1, $2
                      o = notwice(a ? Object : TrueClass, klass, 'type')
                      if a
                        default_style = default_style.guess(arg = a)
                        default_pattern, conv = search(:atype, o) unless default_pattern
                      end
                      ldesc << "--[no-]#{q}"
                      long << (o = q.downcase)
                      not_pattern, not_conv = search(:atype, FalseClass) unless not_style
                      not_style = Switch::NoArgument
                      nolong << 'no-' + o
                    when /^--([^\[\]=\s]*)(.+)?/
                      q, a = $1, $2
                      if a
                        o = notwice(NilClass, klass, 'type')
                        default_style = default_style.guess(arg = a)
                        default_pattern, conv = search(:atype, o) unless default_pattern
                      end
                      ldesc << "--#{q}"
                      long << (o = q.downcase)
                    when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/
                      q, a = $1, $2
                      o = notwice(Object, klass, 'type')
                      if a
                        default_style = default_style.guess(arg = a)
                        default_pattern, conv = search(:atype, o) unless default_pattern
                      end
                      sdesc << "-#{q}"
                      short << Regexp.new(q)
                    when /^-(.)(.+)?/
                      q, a = $1, $2
                      if a
                        o = notwice(NilClass, klass, 'type')
                        default_style = default_style.guess(arg = a)
                        default_pattern, conv = search(:atype, o) unless default_pattern
                      end
                      sdesc << "-#{q}"
                      short << q
                    when /^=/
                      style = notwice(default_style.guess(arg = o), style, 'style')
                      default_pattern, conv = search(:atype, Object) unless default_pattern
                    else
                      desc.push(o)
                  end
            
                end
            
                default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern
                if !(short.empty? and long.empty?)
                  s = (style || default_style).new(pattern || default_pattern, conv, sdesc, ldesc, arg, desc, block)
                elsif !block
                  if style or pattern
                    raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller)
                  end
                  s = desc
                else
                  short << pattern
                  s = (style || default_style).new(pattern, conv, nil, nil, arg, desc, block)
                end
            
                # Make sure required switches are given
                if required && !(default_argv.include?("-#{short[0]}") || default_argv.include?("--#{long[0]}"))
                    @missing_switches ||= [] # Should be placed in initialize if incorporated into Ruby proper
            
                    # This is more clear but ugly and long.
                    #missing = "-#{short[0]}" if !short.empty?
                    #missing = "#{missing} or " if !short.empty? && !long.empty?
                    #missing = "#{missing}--#{long[0]}" if !long.empty?
            
                    # This is less clear and uglier but shorter.
                    missing = "#{"-#{short[0]}" if !short.empty?}#{" or " if !short.empty? && !long.empty?}#{"--#{long[0]}" if !long.empty?}"
            
                    @missing_switches << "Missing switch: #{missing}"
                end
            
                return s, short, long,
                  (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style),
                  nolong
              end
            
            end
            

            【讨论】:

            • pickled_optparse requires Ruby version ~&gt; 1.9.2. :(
            • @DamianNowak - 克隆 github 存储库并修改 pickled_optparse.gemspec 文件的第 36 行。然后执行gem build .\pickled_optparse.gemspec &amp;&amp; gem install .\pickled_optparse-0.1.1.gem,一切顺利。
            【解决方案8】:

            我假设您在这里使用 optparse,尽管相同的技术也适用于其他选项解析库。

            最简单的方法可能是使用您选择的选项解析库解析参数,然后如果 host 的值为 nil,则引发 OptionParser::MissingArgument Exception。

            以下代码说明

            #!/usr/bin/env ruby
            require 'optparse'
            
            options = {}
            
            optparse = OptionParser.new do |opts|
              opts.on('-h', '--host HOSTNAME', "Mandatory Host Name") do |f|
                options[:host] = f
              end
            end
            
            optparse.parse!
            
            #Now raise an exception if we have not found a host option
            raise OptionParser::MissingArgument if options[:host].nil?
            
            
            puts "Host = #{options[:host]}"
            

            用命令行运行这个例子

            ./program -h somehost
            

            简单显示“Host = somehost”

            在缺少 -h 且没有文件名的情况下运行时会产生以下输出

            ./program:15: missing argument:  (OptionParser::MissingArgument)
            

            使用 ./program -h 命令行运行会产生

            /usr/lib/ruby/1.8/optparse.rb:451:in `parse': missing argument: -h (OptionParser::MissingArgument)
              from /usr/lib/ruby/1.8/optparse.rb:1288:in `parse_in_order'
              from /usr/lib/ruby/1.8/optparse.rb:1247:in `catch'
              from /usr/lib/ruby/1.8/optparse.rb:1247:in `parse_in_order'
              from /usr/lib/ruby/1.8/optparse.rb:1241:in `order!'
              from /usr/lib/ruby/1.8/optparse.rb:1332:in `permute!'
              from /usr/lib/ruby/1.8/optparse.rb:1353:in `parse!'
              from ./program:13
            

            【讨论】:

            • 这不是库的原生特性,如果你有更多需要的开关,它就不是很干燥。谢谢。
            • 与其引发神秘的异常,我更喜欢通过调用Kernel.abort 以非零状态退出。它需要一个可选参数,您可以使用它来指定中止的原因。
            • 同意泰德。它根本不是 DRY,应该为它不是 DRY 而感到羞耻。我的第一个命令行应用程序需要一个强制开关,所以不包括在内是不合情理的。
            • @Ted - 我同意。当我查看库 python 有 ruby​​ 时,看起来很可悲。带有示例的清晰文档、足够的灵活性甚至是教程。使用 optparse,我可以获得一些半成品文档和一组有限的选项。 docs.python.org/2.7/library/argparse.html
            • DRY 问题可以轻松解决。例如 ` options = {} required = [] optparse = OptionParser.new do |opts|必需