【问题标题】:Really Cheap Command-Line Option Parsing in RubyRuby 中真正便宜的命令行选项解析
【发布时间】:2010-10-28 05:05:21
【问题描述】:

编辑:请,在回复之前阅读本文底部列出的两个要求。人们不断发布他们的新 gems 和库之类的东西,显然不符合要求。

有时我想非常便宜地将一些命令行选项破解成一个简单的脚本。一种有趣的方法是:

...
$quiet       = ARGV.delete('-d')
$interactive = ARGV.delete('-i')
...
# Deal with ARGV as usual here, maybe using ARGF or whatever.

这不是普通的 Unix 选项语法,因为它会接受选项非选项命令行参数,如“myprog -i foo bar -q”,但我可以接受。 (有些人,例如 Subversion 开发人员,更喜欢这个。有时我也喜欢。)

仅存在或不存在的选项无法比上述更简单地实现。 (一个赋值,一个函数调用,一个副作用。)是否有同样简单的方法来处理带参数的选项,例如“-f filename”?

编辑:

有一点我之前没有说明,因为直到 Trollop 的作者提到该库适合“在一个 [800 行] 文件中”,我才明白我不认为仅适用于简洁的语法,但适用于具有以下特征的技术:

  1. 整个代码可以包含在脚本文件中(不会压倒实际的脚本本身,可能只有几十行),因此可以将单个文件放入bin dir 在任何具有标准 Ruby 1.8.[5-7] 安装的系统上并使用它。如果你不能编写一个没有 require 语句的 Ruby 脚本,并且解析几个选项的代码不到十几行左右,那么你就达不到这个要求。

  2. 代码小而简单,以至于人们可以记住足够多的代码来直接输入代码,而不是从其他地方剪切和粘贴。想一想您在无法访问 Internet 的防火墙服务器的控制台上的情况,并且您想将一个快速脚本组合在一起供客户端使用。我不了解你,但是(除了不符合上述要求之外)即使是 45 行简化的 micro-optparse 也不是我想做的事。

【问题讨论】:

  • 只是对反对 getoptlong 感到好奇?
  • 它的详细程度。使用 getoptlog,有时解析代码的选项比实际执行工作的脚本部分要长。这不仅仅是美学问题,而是维护成本问题。
  • 我不明白脚本包含要求 - getoptlongoptparse 都在标准 ruby​​ 库中,因此在部署脚本时不需要复制它们 - 如果 ruby​​ 有效在那台机器上,然后require 'optparse'require 'getoptlong' 也可以工作。
  • 请参阅stackoverflow.com/questions/21357953/…,以及威廉摩根在下面关于 Trollop 的回答。
  • @CurtSampson 我不敢相信有多少人没有回答你的问题。不管怎样,终于得到了一个关于 3 个帖子的好答案 XD XD

标签: ruby


【解决方案1】:

作为Trollop 的作者,我无法相信人们认为在选项解析器中合理的东西。严重地。令人难以置信。

为什么我必须创建一个扩展其他模块的模块来解析选项?为什么我必须继承任何东西?为什么我必须订阅一些“框架”才能解析命令行?

这是上面的 Trollop 版本:

opts = Trollop::options do
  opt :quiet, "Use minimal output", :short => 'q'
  opt :interactive, "Be interactive"
  opt :filename, "File to process", :type => String
end

就是这样。 opts 现在是带有键 :quiet:interactive:filename 的散列。你可以用它做任何你想做的事。你会得到一个漂亮的帮助页面,格式适合你的屏幕宽度,自动短参数名称,类型检查......你需要的一切。

这是一个文件,因此如果您不想要正式的依赖关系,可以将其放在您的 lib/ 目录中。它具有易于上手的最小 DSL。

LOC 每个选项的人。这很重要。

【讨论】:

  • 顺便说一句,+1 写了 Trollop(这里已经提到过),但请随意将第一段调低一点。
  • 恐怕在这种情况下他有权投诉。当您查看替代方案时:[1][2][3],基本上只是处理一个简单的字符串数组(不是真的,让它沉入其中),您不禁想知道为什么?你从所有这些臃肿中获得了什么?这不是 C,其中字符串是“有问题的”。当然各有各的。 :)
  • 请不要把它调低一点。兄弟,这是一个正义的信条。
  • 请随意将第十个字调低一点。
  • +1 为 Trollop。我将它用于我的测试自动化系统,它只是工作。此外,它很容易编写代码,有时我会重新排列横幅,只是为了体验其中的乐趣。
【解决方案2】:

我和你一样厌恶require 'getopts',主要是因为OptionParser 太棒了:

% cat temp.rb                                                            
require 'optparse'
OptionParser.new do |o|
  o.on('-d') { |b| $quiet = b }
  o.on('-i') { |b| $interactive = b }
  o.on('-f FILENAME') { |filename| $filename = filename }
  o.on('-h') { puts o; exit }
  o.parse!
end
p :quiet => $quiet, :interactive => $interactive, :filename => $filename
% ruby temp.rb                                                           
{:interactive=>nil, :filename=>nil, :quiet=>nil}
% ruby temp.rb -h                                                        
Usage: temp [options]
    -d
    -i
    -f FILENAME
    -h
% ruby temp.rb -d                                                        
{:interactive=>nil, :filename=>nil, :quiet=>true}
% ruby temp.rb -i                                                        
{:interactive=>true, :filename=>nil, :quiet=>nil}
% ruby temp.rb -di                                                       
{:interactive=>true, :filename=>nil, :quiet=>true}
% ruby temp.rb -dif apelad                                               
{:interactive=>true, :filename=>"apelad", :quiet=>true}
% ruby temp.rb -f apelad -i                                              
{:interactive=>true, :filename=>"apelad", :quiet=>nil}

【讨论】:

  • 谢谢,与安装/销售任何非标准代码的需要相比,我看不出这不符合 OP 的要求,特别是考虑到它全部在标准库中
  • 这看起来就像 trollop 版本,只是它不需要额外的文件。
【解决方案3】:

这是我通常使用的标准技术:

#!/usr/bin/env ruby

def usage(s)
    $stderr.puts(s)
    $stderr.puts("Usage: #{File.basename($0)}: [-l <logfile] [-q] file ...")
    exit(2)
end

$quiet   = false
$logfile = nil

loop { case ARGV[0]
    when '-q' then  ARGV.shift; $quiet = true
    when '-l' then  ARGV.shift; $logfile = ARGV.shift
    when /^-/ then  usage("Unknown option: #{ARGV[0].inspect}")
    else break
end; }

# Program carries on here.
puts("quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}")

【讨论】:

  • 回答了这个问题,但是伙计,Trollop 似乎更容易处理。当预制轮子变得如此光滑时,为什么要重新发明轮子?
  • 预制轮不平滑。再次仔细阅读问题,注意要求。
  • +1 有时您需要重新发明轮子,因为您不想或根本不能使用其他依赖项,例如 Trollop。
  • Trollop 不需要作为 gem 安装。您只需将一个文件放到lib 文件夹或代码中即可使用它,甚至无需触摸 ruby​​gems。
  • 对我来说,我必须将when /^-/ then usage("Unknown option: #{ARGV[0].inspect}") 更改为when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}") 否则会进入无限循环使用
【解决方案4】:

既然似乎没有人提到它,而且标题 确实 指的是 cheap 命令行解析,为什么不让 Ruby 解释器为您完成这项工作呢?如果您通过-s 开关(例如,在您的shebang 中),您将免费获得简单的开关,分配给单字母全局变量。这是您使用该开关的示例:

#!/usr/bin/env ruby -s
puts "#$0: Quiet=#$q Interactive=#$i, ARGV=#{ARGV.inspect}"

这是我将其保存为 ./test 并 chmod 它+x 时的输出:

$ ./test
./test: Quiet= Interactive=, ARGV=[]
$ ./test -q foo
./test: Quiet=true Interactive=, ARGV=["foo"]
$ ./test -q -i foo bar baz
./test: Quiet=true Interactive=true, ARGV=["foo", "bar", "baz"]
$ ./test -q=very foo
./test: Quiet=very Interactive=, ARGV=["foo"]

详情请见ruby -h

必须尽可能便宜。如果您尝试像-: 这样的开关,它将引发 NameError,因此那里有一些验证。当然,在非开关参数之后不能有任何开关,但如果你需要一些花哨的东西,你真的应该至少使用 OptionParser。事实上,这种技术唯一让我恼火的是,当你访问一个未设置的全局变量时,你会得到一个警告(如果你启用了它们),但它仍然是错误的,所以它适用于一次性工具并且快速脚本。

FelipeC 在 "How to do really cheap command-line option parsing in Ruby" 的 cmets 中指出的一个警告是,您的 shell 可能不支持 3-token shebang;您可能需要将 /usr/bin/env ruby -w 替换为您的 ruby​​ 的实际路径(如 /usr/local/bin/ruby -w),或者从包装脚本或其他方式运行它。

【讨论】:

  • 谢谢 :) 我当然希望他在过去两年没有等待这个答案。
  • 过去两年我确实一直在等待这个答案。 :-) 更严重的是,这是我一直在寻找的那种聪明的想法。警告有点烦人,但我可以想办法减轻这种情况。
  • 很高兴我可以(最终)提供帮助,@CurtSampson,MRI 的标志是直接从 Perl 中撕下来的,在那里它们往往被无偿地用于 shell 单行。如果答案对您仍然有用,请随时接受。 :)
  • 您不能在 Linux 的 shebang 中使用多个参数。 /usr/bin/env: ‘ruby -s’: 没有这样的文件或目录
【解决方案5】:

我构建了micro-optparse 来满足对简短但易于使用的选项解析器的明显需求。它的语法类似于 Trollop,并且只有 70 行。如果您不需要验证并且可以不使用空行,则可以将其减少到 45 行。我认为这正是您想要的。

简短示例:

options = Parser.new do |p|
  p.version = "fancy script version 1.0"
  p.option :verbose, "turn on verbose mode"
  p.option :number_of_chairs, "defines how many chairs are in the classroom", :default => 1
  p.option :room_number, "select room number", :default => 2, :value_in_set => [1,2,3,4]
end.process!

使用-h--help 调用脚本将打印

Usage: micro-optparse-example [options]
    -v, --[no-]verbose               turn on verbose mode
    -n, --number-of-chairs 1         defines how many chairs are in the classroom
    -r, --room-number 2              select room number
    -h, --help                       Show this message
    -V, --version                    Print version

它检查输入是否与默认值的类型相同,生成短访问器和长访问器,如果给出了无效参数,则打印描述性错误消息等等。

compared several option-parser 使用每个选项解析器来解决我遇到的问题。 您可以使用这些示例和我的摘要来做出信息丰富的决定。 随意将更多实现添加到列表中。 :)

【讨论】:

  • 图书馆本身看起来可能很棒。但是,将行数与 Trollop 进行比较不是不诚实,因为您依赖并需要 optparse,即(给予或接受)1937 行。
  • 比较行数是绝对可以的,因为optparse 是一个默认库,即它随每个 ruby​​ 安装一起提供。 Trollop 是第三方库,因此每次要将其包含在项目中时都必须导入完整的代码。 µ-optparse 总是只需要 ~70 行,因为 optparse 已经存在了。
【解决方案6】:

我完全理解您为什么要避免使用 optparse - 它可能会变得太多。但是有一些更“轻量级”的解决方案(与 OptParse 相比)作为库提供,但足够简单,可以让单个 gem 安装变得有价值。

例如,查看this OptiFlag example。只需几行即可进行处理。为您的案例量身定制的略微截断的示例:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

ARGV.flags.f # => .. whatever ..

tons of customized examples too。我记得使用了另一个更简单的方法,但它现在已经逃脱了我,但如果我找到它,我会回来并在此处添加评论。

【讨论】:

  • 随时编辑您的答案以更好地适应已澄清的问题。
【解决方案7】:

这是我用的非常非常便宜的参数:

def main
  ARGV.each { |a| eval a }
end

main

所以如果你运行programname foo bar,它会调用foo,然后调用bar。它对于一次性脚本很方便。

【讨论】:

    【解决方案8】:

    你可以试试这样的:

    if( ARGV.include( '-f' ) )
      file = ARGV[ARGV.indexof( '-f' ) + 1 )]
      ARGV.delete('-f')
      ARGV.delete(file)
    end
    

    【讨论】:

      【解决方案9】:

      你考虑过 wycats 的Thor 吗?我认为它比 optparse 干净得多。如果您已经编写了脚本,则可能需要更多的工作来格式化它或为 thor 重构它,但它确实使处理选项变得非常简单。

      这是自述文件中的示例 sn-p:

      class MyApp < Thor                                                # [1]
        map "-L" => :list                                               # [2]
      
        desc "install APP_NAME", "install one of the available apps"    # [3]
        method_options :force => :boolean, :alias => :optional          # [4]
        def install(name)
          user_alias = options[:alias]
          if options.force?
            # do something
          end
          # ... other code ...
        end
      
        desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
        def list(search = "")
          # list everything
        end
      end
      

      Thor 自动映射命令如下:

      app install myname --force
      

      转换为:

      MyApp.new.install("myname")
      # with {'force' => true} as options hash
      
      1. 从 Thor 继承以将类转换为选项映射器
      2. 将其他无效标识符映射到特定方法。在这种情况下,将 -L 转换为 :list
      3. 在下面描述该方法。第一个参数是使用信息,第二个参数是描述。
      4. 提供任何其他选项。这些将从 - 和 - 参数编组。在这种情况下,添加了 --force 和 -f 选项。

      【讨论】:

      • 我喜欢命令映射的东西,因为我经常使用带有一堆子命令的单个二进制文件。尽管如此,尽管您已经远离“光”。你能找到一种更简单的方法来表达同样的功能吗?如果您不需要打印--help 输出怎么办?如果“head myprogram.rb”是帮助输出怎么办?
      【解决方案10】:

      这是我最喜欢的快速而肮脏的选项解析器:

      case ARGV.join
      when /-h/
        puts "help message"
        exit
      when /-opt1/
        puts "running opt1"
      end
      

      选项是正则表达式,所以“-h”也会匹配“--help”。

      可读、易记、无需外部库、代码最少。

      【讨论】:

      • 是的。如果这是一个问题,您可以添加更多正则表达式,例如 /-h(\b|elp)
      【解决方案11】:

      Trollop 相当便宜。

      【讨论】:

      • 那就是,trollop.rubyforge.org>。我想我更喜欢它,虽然我真的不是在寻找图书馆。
      • 没错,它是一个库。然而,在 gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
      • 我有点想,如果我要使用“库”,也许 30-50 行会很好。但是话又说回来,我想一旦你得到一个充满代码的单独文件,API 设计比行数更重要。不过,我不确定是否要将其包含在一次性脚本中,我只想将其放入随机系统上的 bin 目录中。
      • 你搞错了:关键是要避免不得不采用更复杂的部署策略。
      • 您大错特错,因为您完全误解(或忽略了)提问者的需求和意图。我建议您仔细阅读问题,尤其是最后两点。
      【解决方案12】:

      如果您想要一个简单的命令行解析器来处理键/值命令而不使用 gem:

      但这在您始终拥有键/值对的情况下才有效。

      # example
      # script.rb -u username -p mypass
      
      # check if there are even set of params given
      if ARGV.count.odd? 
          puts 'invalid number of arguments'
          exit 1
      end
      
      # holds key/value pair of cl params {key1 => value1, key2 => valye2, ...}
      opts = {} 
      
      (ARGV.count/2).times do |i|
          k,v = ARGV.shift(2)
          opts[k] = v # create k/v pair
      end
      
      # set defaults if no params are given
      opts['-u'] ||= 'root'
      
      # example use of opts
      puts "username:#{opts['-u']} password:#{opts['-p']}"
      

      如果您不需要任何检查,您可以使用:

      opts = {} 
      
      (ARGV.count/2).times do |i|
          k,v = ARGV.shift(2)
          opts[k] = v # create k/v pair
      end
      

      【讨论】:

        【解决方案13】:

        这是我在大多数脚本顶部使用的代码 sn-p:

        arghash = Hash.new.tap { |h| # Parse ARGV into a hash
            i = -1                      
            ARGV.map{  |s| /(-[a-zA-Z_-])?([^=]+)?(=)?(.+)?/m.match(s).to_a }
             .each{ |(_,a,b,c,d)| h[ a ? "#{a}#{b}#{c}" : (i+=1) ] =
                                     (a ? (c ? "#{d}" : true) : "#{b}#{c}#{d}") 
                  }
            [[:argc,Proc.new  {|| h.count{|(k,_)| !k.is_a?(String)}}],
             [:switches, Proc.new {|| h.keys.select{|k| k[0] == '-' }}]
            ].each{|(n,p)| h.define_singleton_method(n,&p) }
        }
        

        我也讨厌在我的快速而肮脏的脚本中要求额外的文件。我的解决方案非常接近您的要求。我在我的任何脚本的顶部粘贴了一个 10 行的 sn-p 代码,这些脚本解析命令行并粘贴位置参数并切换到 Hash 对象(通常分配给我命名为 arghash 的对象strong> 在下面的示例中)。

        这是您可能想要解析的示例命令行...

        ./myexampleprog.rb -s -x=15 --longswitch arg1 --longswitch2=val1 arg2

        这会变成这样的哈希。

         { 
           '-s' => true, 
           '-x=' => '15', 
           '--longswitch' => true, 
           '--longswitch2=' => 'val1', 
           0 => 'arg1', 
           1 => 'arg2'
         }
        

        除此之外,Hash 中还添加了两个方便的方法:

        • argc() 将返回非开关参数的计数。
        • switches() 将返回一个数组,其中包含当前开关的键

        这是为了允许一些快速而肮脏的东西,比如......

        • 验证无论传入的开关如何(arghash.argc == 2),我都有正确数量的位置参数
        • 通过位置参数的相对位置访问位置参数,无论开关是否出现在位置参数之前或散布在位置参数中(例如,arghash[1] 总是获得第二个非开关参数)。
        • 支持命令行中的赋值开关,例如“--max=15”,arghash['--max='] 可以访问它,给出的示例命令行产生的值为“15”。
        • 使用非常简单的符号(例如 arghash['-s'])测试命令行中开关是否存在,如果存在则评估为 true,如果不存在则评估为 nil。
        • 使用诸如

          之类的集合操作测试是否存在开关或替代开关

          puts USAGETEXT if !(%w(-h --help) &amp; arghash.switches()).empty?

        • 使用集合操作识别无效开关的使用,例如

          puts "Invalid switch found!" if !(arghash.switches - %w(-valid1 -valid2)).empty?

        • 使用简单的Hash.merge() 为缺少的参数指定默认值,例如下面的示例,如果未设置一个值,则为 -max= 填充一个值,如果未传递,则添加第四个位置参数。

          with_defaults = {'-max=' =&gt; 20, 3 =&gt; 'default.txt'}.merge(arghash)

        【讨论】:

        • (我已经编辑了这个以清理改进代码格式,主要是使用对齐来使块和控制结构更加清晰,我觉得这在标点符号密集的东西中尤其重要。但是如果你讨厌新格式,请随时撤消编辑。)
        • 这非常好,如果不是世界上最容易阅读的东西。我喜欢它还表明更改参数“语法”(此处,允许位置参数后的选项和禁止选项参数,除非使用 =)可以对您需要的代码产生影响。
        • 感谢您重新格式化。阅读起来绝对是晦涩难懂的,而且为了清晰起见,人们可以轻松地交换代码长度。现在我或多或少地信任这段代码,我把它当作宝石一样对待,我从不试图弄清楚它在幕后做了什么(所以既然我有信任,清晰度就不再重要了)。
        【解决方案14】:

        这与接受的答案非常相似,但使用的是ARGV.delete_if,这是我在simple parser 中使用的。唯一真正的区别是带参数的选项必须在一起(例如-lfile)。

        def usage
          "usage: #{File.basename($0)}: [-l<logfile>] [-q] file ..."
        end
        
        ARGV.delete_if do |cur|
          next false if cur[0] != '-'
          case cur
          when '-q'
            $quiet = true
          when /^-l(.+)$/
            $logfile = $1
          else
            $stderr.puts "Unknown option: #{cur}"
            $stderr.puts usage
            exit 1
          end
        end
        

        【讨论】:

          【解决方案15】:

          显然@WilliamMorgan 和我的想法相似。我昨晚刚刚在 Github 上发布了一个类似于 Trollop 的库(命名如何?),在 Github 上搜索了 OptionParser 后,请参阅 Switches

          有一些差异,但理念是相同的。一个明显的区别是 Switches 依赖于 OptionParser。

          【讨论】:

            【解决方案16】:

            我正在开发my own option parser gem called Acclaim

            我写它是因为我想创建 git 风格的命令行界面,并且能够将每个命令的功能清晰地分离到单独的类中,但它也可以在没有整个命令框架的情况下使用:

            (options = []) << Acclaim::Option.new(:verbose, '-v', '--verbose')
            values = Acclaim::Option::Parser.new(ARGV, options).parse!
            puts 'Verbose.' if values.verbose?
            

            目前还没有稳定版本,但我已经实现了一些功能,例如:

            • 自定义选项解析器
            • 灵活解析选项的参数,允许最小和可选
            • 支持多种选项样式
            • 在同一选项的多个实例上替换、追加或提升
            • 自定义选项处理程序
            • 自定义类型处理程序
            • 通用标准库类的预定义处理程序

            非常强调命令,因此对于简单的命令行解析可能有点繁重,但它运行良好,我一直在我的所有项目中使用它。如果您对命令接口方面感兴趣,请查看the project's GitHub page 了解更多信息和示例。

            【讨论】:

            • 我强烈推荐 Acclaim。它易于使用,并且具有您需要的所有选项。
            【解决方案17】:

            假设一个命令最多有一个操作和任意数量的选项,如下所示:

            cmd.rb
            cmd.rb action
            cmd.rb action -a -b ...
            cmd.rb action -ab ...
            

            没有验证的解析可能是这样的:

            ACTION = ARGV.shift
            OPTIONS = ARGV.join.tr('-', '')
            
            if ACTION == '***'
              ...
              if OPTIONS.include? '*'
                ...
              end
              ...
            end
            

            【讨论】:

              【解决方案18】:

              https://github.com/soveran/clap

              other_args = Clap.run ARGV,
                "-s" => lambda { |s| switch = s },
                "-o" => lambda { other = true }
              

              46LOC(在 1.0.0),不依赖于外部选项解析器。完成工作。可能没有其他功能那么全,但它是 46LOC。

              如果你检查代码,你可以很容易地复制底层技术——如果你真的不想要外部库,分配 lambda 并使用 arity 来确保正确数量的 args 跟随标志。

              简单。便宜。


              编辑:基本概念归结为我想您可以将其复制/粘贴到脚本中以制作合理的命令行解析器。这绝对是不是我会记住的东西,但是使用 lambda arity 作为廉价的解析器是一个新颖的想法:

              flag = false
              option = nil
              opts = {
                "--flag" => ->() { flag = true },
                "--option" => ->(v) { option = v }
              }
              
              argv = ARGV
              args = []
              
              while argv.any?
                item = argv.shift
                flag = opts[item]
              
                if flag
                  raise ArgumentError if argv.size < arity
                  flag.call(*argv.shift(arity))
                else
                  args << item
                end
              end
              
              # ...do stuff...
              

              【讨论】:

              • 请阅读问题末尾的第 1 点。如果您无法在此处的答案中输入 all 必要的代码,则这不是问题的答案。
              • 好点!我认为当时我认为 lib 足够小,您可以将整个内容复制/粘贴到您正在处理的任何脚本中,而无需外部依赖,但它绝对不是一个干净的单行,我会致力于记忆实现您的第 2 点。我确实认为基本概念足够新颖和酷,因此我继续制作了一个简化版本,可以更恰当地回答您的问题。
              【解决方案19】:

              我创建了一个非常简单但有用的解析器:parseopt。它使用 Git 的内部选项解析器作为灵感,也使用 Ruby 的 OptionParser。

              看起来像这样:

              opts = ParseOpt.new
              opts.usage = 'git foo'
              
              opts.on('b', 'bool', 'Boolean') do |v|
               $bool = v
              end
              
              opts.on('s', 'string', 'String') do |v|
               $str = v
              end
              
              opts.on('n', 'number', 'Number') do |v|
               $num = v.to_i
              end
              
              opts.parse
              

              【讨论】:

              • 你甚至没有检查代码。我已经把解析代码去掉了另一个答案。
              • @cjs 第一个回答提到了Trollop,他们需要把Trollop的整个代码放上去吗?
              • 是的,这就是我否决这个答案的原因。 (这是“第一个”答案是因为似乎很多人对这个问题的答案进行投票是根据与问题中的要求不同的一组要求进行投票的。对此我无能为力,但你会注意到接受的答案不是投票最高的答案。)
              猜你喜欢
              • 2013-07-19
              • 2013-05-16
              • 2013-01-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-02-28
              • 1970-01-01
              • 2012-01-06
              相关资源
              最近更新 更多