OptionParser可以跳过未知选项,稍后在Ruby程序中处理吗?

有没有办法在一个Ruby程序中多次启动OptionParser ,每个程序都有不同的选项?

例如:

$ myscript.rb --subsys1opt a --subsys2opt b 

在这里,myscript.rb将使用subsys1和subsys2,将它们的选项处理逻辑委托给它们,可能是首先处理’a’的序列,然后是单独的OptionParser对象中的’b’; 每次选择仅与该上下文相关的选项。 最后阶段可以检查在每个部件处理完他们之后没有任何未知数。

用例是:

  1. 在松散耦合的前端程序中,各种组件具有不同的参数,我不希望“main”知道所有内容,只是为每个部分委派参数/选项集。

  2. 将一些更大的系统(如RSpec)嵌入到我的应用程序中,我只需通过命令行通过他们的选项,而不需要我的包装器知道这些。

我也可以使用一些分隔符选项,比如某些Java应用程序中的----vmargs

在Unix世界中有许多类似事物的真实例子(startx / X,git plumbing和瓷器),其中一层处理一些选项但将其余部分传播到下层。

开箱即用,这似乎不起作用。 每个OptionParse.parse! 呼叫将进行详尽的处理,对其不知道的任何事情失败。 我想我很乐意跳过未知选项。

任何提示,也许是替代方法都是受欢迎的。

假设解析器运行的顺序定义良好,您可以将额外选项存储在临时全局变量中并运行OptionParser#parse! 在每组选项上。

最简单的方法是使用你提到的分隔符。 假设第二组参数通过分隔符与第一组参数分开-- 。 然后这将做你想要的:

 opts = OptionParser.new do |opts| # set up one OptionParser here end both_args = $*.join(" ").split(" -- ") $extra_args = both_args[1].split(/\s+/) opts.parse!(both_args[0].split(/\s+/)) 

然后,在第二个代码/上下文中,您可以执行以下操作:

 other_opts = OptionParser.new do |opts| # set up the other OptionParser here end other_opts.parse!($extra_args) 

或者,这可能是“更合适”的方法,你可以简单地使用OptionParser#parse ,没有感叹号,它不会从$*数组中删除命令行开关,并确保那里并非两个集中定义的选项相同。 我建议不要手动修改$*数组,因为如果你只是看第二部分会让你的代码更难理解,但你可以这样做。 在这种情况下,您必须忽略无效选项:

 begin opts.parse rescue OptionParser::InvalidOption puts "Warning: Invalid option" end 

正如评论中指出的那样,第二种方法实际上并不起作用。 但是,如果你必须修改$*数组,你可以这样做:

 tmp = Array.new while($*.size > 0) begin opts.parse! rescue OptionParser::InvalidOption => e tmp.push(e.to_s.sub(/invalid option:\s+/,'')) end end tmp.each { |a| $*.push(a) } 

它不仅仅是一点点黑客,但它应该做你想要的。

我需要一个不会抛出OptionParser::InvalidOption的解决方案,并且无法在当前答案中找到优雅的解决方案。 这个猴子补丁是基于其他一个答案,但清理它,使其更像当前的order! 语义。 但请参阅下文,了解多次传递选项解析所固有的未解决问题。

 class OptionParser # Like order!, but leave any unrecognized --switches alone def order_recognized!(args) extra_opts = [] begin order!(args) { |a| extra_opts << a } rescue OptionParser::InvalidOption => e extra_opts << e.args[0] retry end args[0, 0] = extra_opts end end 

order!一样工作order! 除了抛出InvalidOption ,它在ARGV留下了无法识别的开关。

RSpec测试:

 describe OptionParser do before(:each) do @parser = OptionParser.new do |opts| opts.on('--foo=BAR', OptionParser::DecimalInteger) { |f| @found << f } end @found = [] end describe 'order_recognized!' do it 'finds good switches using equals (--foo=3)' do argv = %w(one two --foo=3 three) @parser.order_recognized!(argv) expect(@found).to eq([3]) expect(argv).to eq(%w(one two three)) end it 'leaves unknown switches alone' do argv = %w(one --bar=2 two three) @parser.order_recognized!(argv) expect(@found).to eq([]) expect(argv).to eq(%w(one --bar=2 two three)) end it 'leaves unknown single-dash switches alone' do argv = %w(one -bar=2 two three) @parser.order_recognized!(argv) expect(@found).to eq([]) expect(argv).to eq(%w(one -bar=2 two three)) end it 'finds good switches using space (--foo 3)' do argv = %w(one --bar=2 two --foo 3 three) @parser.order_recognized!(argv) expect(@found).to eq([3]) expect(argv).to eq(%w(one --bar=2 two three)) end it 'finds repeated args' do argv = %w(one --foo=1 two --foo=3 three) @parser.order_recognized!(argv) expect(@found).to eq([1, 3]) expect(argv).to eq(%w(one two three)) end it 'maintains repeated non-switches' do argv = %w(one --foo=1 one --foo=3 three) @parser.order_recognized!(argv) expect(@found).to eq([1, 3]) expect(argv).to eq(%w(one one three)) end it 'maintains repeated unrecognized switches' do argv = %w(one --bar=1 one --bar=3 three) @parser.order_recognized!(argv) expect(@found).to eq([]) expect(argv).to eq(%w(one --bar=1 one --bar=3 three)) end it 'still raises InvalidArgument' do argv = %w(one --foo=bar) expect { @parser.order_recognized!(argv) }.to raise_error(OptionParser::InvalidArgument) end it 'still raises MissingArgument' do argv = %w(one --foo) expect { @parser.order_recognized!(argv) }.to raise_error(OptionParser::MissingArgument) end end end 

问题:通常OptionParser允许缩写选项,前提是有足够的字符来唯一标识预期的选项。 多阶段的解析选项会破坏这一点,因为OptionParser在第一遍中没有看到所有可能的参数。 例如:

 describe OptionParser do context 'one parser with similar prefixed options' do before(:each) do @parser1 = OptionParser.new do |opts| opts.on('--foobar=BAR', OptionParser::DecimalInteger) { |f| @found_foobar << f } opts.on('--foo=BAR', OptionParser::DecimalInteger) { |f| @found_foo << f } end @found_foobar = [] @found_foo = [] end it 'distinguishes similar prefixed switches' do argv = %w(--foo=3 --foobar=4) @parser1.order_recognized!(argv) expect(@found_foobar).to eq([4]) expect(@found_foo).to eq([3]) end end context 'two parsers in separate passes' do before(:each) do @parser1 = OptionParser.new do |opts| opts.on('--foobar=BAR', OptionParser::DecimalInteger) { |f| @found_foobar << f } end @parser2 = OptionParser.new do |opts| opts.on('--foo=BAR', OptionParser::DecimalInteger) { |f| @found_foo << f } end @found_foobar = [] @found_foo = [] end it 'confuses similar prefixed switches' do # This is not generally desirable behavior argv = %w(--foo=3 --foobar=4) @parser1.order_recognized!(argv) @parser2.order_recognized!(argv) expect(@found_foobar).to eq([3, 4]) expect(@found_foo).to eq([]) end end end 

对于后代,您可以通过order!执行此操作order! 方法:

 option_parser.order!(args) do |unrecognized_option| args.unshift(unrecognized_option) end 

此时, args已被修改 – 所有已知选项都由option_parser使用和处理 – 并且可以传递给不同的选项解析器:

 some_other_option_parser.order!(args) do |unrecognized_option| args.unshift(unrecognized_option) end 

显然,这个解决方案依赖于顺序,但是你要做的事情有点复杂和不同寻常。

可能是一个很好的折衷方案是在命令行上使用--停止处理。 这样做会留下随之而来的args --更多选项或只是常规参数。

我遇到了同样的问题,我找到了以下解决方案:

 options = ARGV.dup
剩余= []
 while!options.empty?
  开始
     head = options.shift
     remaining.concat(parser.parse([头]))
  救援OptionParser :: InvalidOption
    剩下的<<头
    重试
  结束
结束

另一个依赖于parse!解决方案parse! 即使抛出错误,也会对参数列表产生副作用。

让我们定义一个方法,它尝试使用用户定义的解析器扫描一些参数列表,并在抛出InvalidOption错误时递归调用自身,保存无效选项以便稍后使用最终参数:

 def parse_known_to(parser, initial_args=ARGV.dup) other_args = [] # this contains the unknown options rec_parse = Proc.new { |arg_list| # in_method defined proc begin parser.parse! arg_list # try to parse the arg list rescue OptionParser::InvalidOption => e other_args += e.args # save the unknown arg while arg_list[0] && arg_list[0][0] != "-" # certainly not perfect but other_args << arg_list.shift # quick hack to save any parameters end rec_parse.call arg_list # call itself recursively end } rec_parse.call initial_args # start the rec call other_args # return the invalid arguments end my_parser = OptionParser.new do ... end other_options = parse_known_to my_parser 

我也需要同样的…它花了我一段时间,但一个相对简单的方法最终工作得很好。

 options = { :input_file => 'input.txt', # default input file } opts = OptionParser.new do |opt| opt.on('-i', '--input FILE', String, 'Input file name', 'Default is %s' % options[:input_file] ) do |input_file| options[:input_file] = input_file end opt.on_tail('-h', '--help', 'Show this message') do puts opt exit end end extra_opts = Array.new orig_args = ARGV.dup begin opts.parse!(ARGV) rescue OptionParser::InvalidOption => e extra_opts << e.args retry end args = orig_args & ( ARGV | extra_opts.flatten ) 

“args”将包含所有命令行参数,而不包含已经解析为“options”哈希的参数。 我将这个“args”传递给要从这个ruby脚本调用的外部程序。

当我编写一个包含ruby gem的脚本时遇到了类似的问题,它需要自己的选项并传递参数。

我提出了以下解决方案,其中它支持包装工具的参数选项 。 它的工作原理是通过第一个optparser解析它,并将它不能使用的内容分成一个单独的数组(可以用另一个optparse再次重新解析)。

 optparse = OptionParser.new do |opts| # OptionParser settings here end arguments = ARGV.dup secondary_arguments = [] first_run = true errors = false while errors || first_run errors = false first_run = false begin optparse.order!(arguments) do |unrecognized_option| secondary_arguments.push(unrecognized_option) end rescue OptionParser::InvalidOption => e errors = true e.args.each { |arg| secondary_arguments.push(arg) } arguments.delete(e.args) end end primary_arguments = ARGV.dup secondary_arguments.each do |cuke_arg| primary_arguments.delete(cuke_arg) end puts "Primary Args: #{primary_arguments}" puts "Secondary Args: #{secondary_args}" optparse.parse(primary_arguments) # Can parse the second list here, if needed # optparse_2.parse(secondary_args) 

可能不是最好或最有效的方式,但它对我有用。

我刚刚离开了Python。 Python的ArgumentParser有很好的方法parse_known_args() 。 但它仍然不接受第二个论点,例如:

 $ your-app -x 0 -x 1 

第一个-x 0是你的应用程序的参数。 第二个-x 1可以属于您需要转发的目标应用。 在这种情况下, ArgumentParser将引发错误。

现在回到Ruby,你可以使用#order 。 幸运的是,它接受无限重复的参数。 例如,你需要-a-b 。 你的目标应用需要另一个-a 一个强制性参数(注意没有前缀- / -- )。 通常#parse会忽略强制参数。 但是对于#order ,你会得到其余的 – 很棒。 请注意 ,您必须先传递自己的应用程序参数,然后再传递目标应用程序的参数。

 $ your-app -a 0 -b 1 -a 2 some 

代码应该是:

 require 'optparse' require 'ostruct' # Build default arguments options = OpenStruct.new options.a = -1 options.b = -1 # Now parse arguments target_app_argv = OptionParser.new do |opts| # Handle your own arguments here # ... end.order puts ' > Options = %s' % [options] puts ' > Target app argv = %s' % [target_app_argv] 

田田:-)

我的尝试:

 def first_parse left = [] begin @options.order!(ARGV) do |opt| left << opt end rescue OptionParser::InvalidOption => e e.recover(args) left << args.shift retry end left end 

在我的情况下,我想扫描选项并选择任何可能设置调试级别,输出文件等的预定义选项。然后我将加载可能添加到选项的自定义处理器。 加载完所有自定义处理器后,我调用@options.parse!(left)来处理剩余的选项。 请注意--help内置于选项中,因此如果您不想第一次识别帮助,则需要在创建OptParser之前执行'OptionParser :: Officious.delete('help')'然后添加自己的帮助选项

解析选项直到第一个未知选项…可能会多次调用该块,因此请确保安全…

 options = { :input_file => 'input.txt', # default input file } opts = OptionParser.new do |opt| opt.on('-i', '--input FILE', String, 'Input file name', 'Default is %s' % options[:input_file] ) do |input_file| options[:input_file] = input_file end opt.on_tail('-h', '--help', 'Show this message') do puts opt exit end end original = ARGV.dup leftover = [] loop do begin opts.parse(original) rescue OptionParser::InvalidOption leftover.unshift(original.pop) else break end end puts "GOT #{leftover} -- #{original}"