2014-10-17 31 views
31

我想從命令行調用一個Ruby腳本,並傳入參數是鍵/值對。在Ruby腳本中解析命令行參數

命令行調用:

$ ruby my_script.rb --first_name=donald --last_name=knuth 

my_script.rb:

puts args.first_name + args.last_name 

什麼是標準Ruby的方式來做到這一點?在其他語言中,我通常必須使用選項解析器。在紅寶石中,我看到我們有ARGF.read,但這似乎並沒有像這個例子中的鍵/值對那樣工作。

OptionParser看起來很有希望,但我不知道它是否真的支持這種情況。

+0

https://www.ruby-toolbox.com/categories/CLI_Option_Parsers – 2014-10-18 00:06:13

+0

如果我沒有弄錯,Highline好像是l ike helper的功能是要求用戶輸入。所以我會用Highline讓我的控制檯說'First Name:'並等待他們的輸入。我應該看一個特定的功能嗎? – 2014-10-18 00:09:10

+1

有許多寶石可以選擇;該網站對這些庫進行分類並按照流行度進行排序。我甚至寫了我自己的gem,叫做'acclaim',它確實支持'--option = value'語法。不過,我還沒有時間來維護自由軟件項目。你應該選擇一個更好支持的庫。 – 2014-10-18 00:12:56

回答

23

根據@MartinCortez的回答,這裏有一個簡短的一次性關鍵字/值對的散列值,其中值必須與=符號連接。它還支持標誌參數沒有值:

args = Hash[ ARGV.join(' ').scan(/--?([^=\s]+)(?:=(\S+))?/) ] 

...或可替代......

args = Hash[ ARGV.flat_map{|s| s.scan(/--?([^=\s]+)(?:=(\S+))?/) } ] 

-x=foo -h --jim=jam調用返回{"x"=>"foo", "h"=>nil, "jim"=>"jam"}所以你可以做這樣的事情:

puts args['jim'] if args.key?('h') 
#=> jam 

雖然有多個庫可以處理這個問題 - 包括GetoptLong included with Ruby - 我個人更喜歡自己推出。下面是我使用的模式,這使得它相當通用的,不依賴於特定的使用格式,並具有足夠的靈活性,允許以不同的順序混雜的標誌,選項和必需的參數:

USAGE = <<ENDUSAGE 
Usage: 
    docubot [-h] [-v] [create [-s shell] [-f]] directory [-w writer] [-o output_file] [-n] [-l log_file] 
ENDUSAGE 

HELP = <<ENDHELP 
    -h, --help  Show this help. 
    -v, --version Show the version number (#{DocuBot::VERSION}). 
    create   Create a starter directory filled with example files; 
        also copies the template for easy modification, if desired. 
    -s, --shell  The shell to copy from. 
        Available shells: #{DocuBot::SHELLS.join(', ')} 
    -f, --force  Force create over an existing directory, 
        deleting any existing files. 
    -w, --writer  The output type to create [Defaults to 'chm'] 
        Available writers: #{DocuBot::Writer::INSTALLED_WRITERS.join(', ')} 
    -o, --output  The file or folder (depending on the writer) to create. 
        [Default value depends on the writer chosen.] 
    -n, --nopreview Disable automatic preview of .chm. 
    -l, --logfile Specify the filename to log to. 

ENDHELP 

ARGS = { :shell=>'default', :writer=>'chm' } # Setting default values 
UNFLAGGED_ARGS = [ :directory ]    # Bare arguments (no flag) 
next_arg = UNFLAGGED_ARGS.first 
ARGV.each do |arg| 
    case arg 
    when '-h','--help'  then ARGS[:help]  = true 
    when 'create'   then ARGS[:create] = true 
    when '-f','--force'  then ARGS[:force]  = true 
    when '-n','--nopreview' then ARGS[:nopreview] = true 
    when '-v','--version' then ARGS[:version] = true 
    when '-s','--shell'  then next_arg = :shell 
    when '-w','--writer' then next_arg = :writer 
    when '-o','--output' then next_arg = :output 
    when '-l','--logfile' then next_arg = :logfile 
    else 
     if next_arg 
     ARGS[next_arg] = arg 
     UNFLAGGED_ARGS.delete(next_arg) 
     end 
     next_arg = UNFLAGGED_ARGS.first 
    end 
end 

puts "DocuBot v#{DocuBot::VERSION}" if ARGS[:version] 

if ARGS[:help] or !ARGS[:directory] 
    puts USAGE unless ARGS[:version] 
    puts HELP if ARGS[:help] 
    exit 
end 

if ARGS[:logfile] 
    $stdout.reopen(ARGS[:logfile], "w") 
    $stdout.sync = true 
    $stderr.reopen($stdout) 
end 

# etc. 
+0

這很好,但如果你也使用內建的'ARGF'來通過文件名/標準輸入讀取流,你需要確保在使用'ARGF.read'之前使用來自'ARGV'的參數,否則你會結束於「沒有這樣的文件或目錄」錯誤。 – Lauren 2017-07-20 10:58:41

2

標準的Ruby Regexpmyscript.rb的位:

args = {} 

ARGV.each do |arg| 
    match = /--(?<key>.*?)=(?<value>.*)/.match(arg) 
    args[match[:key]] = match[:value] # e.g. args['first_name'] = 'donald' 
end 

puts args['first_name'] + ' ' + args['last_name'] 

和命令行上:

$ ruby script.rb --first_name=donald --last_name=knuth

產地:

$ donald knuth

+1

這很不錯!在'-f foo'和'-x -y bar'之間使用'='來消除歧義的要求對我來說是一個非現實世界的啓動者,但它是一個很好的快速入侵。 – Phrogz 2014-10-18 00:56:04

+1

是的。我只是按照上面的格式。基於這個問題,我不確定他是否關心消歧。 – 2014-10-18 00:57:08

+0

這看起來很棒,但你會如何定義's'? 'ARGV.join.to_s'? – 2014-10-18 01:08:09

72

Ruby的內置OptionParser做這很好。與OpenStruct結合它和你家免費:

require 'optparse' 

options = {} 
OptionParser.new do |opt| 
    opt.on('--first_name FIRSTNAME') { |o| options[:first_name] = o } 
    opt.on('--last_name LASTNAME') { |o| options[:last_name] = o } 
end.parse! 

puts options 

options將包含參數和值作爲哈希。

保存和運行,在不帶參數的結果在命令行:

$ ruby test.rb 
{} 

帶參數運行它:

$ ruby test.rb --first_name=foo --last_name=bar 
{:first_name=>"foo", :last_name=>"bar"} 

這個例子是使用哈希包含的選項,但你可以使用OpenStruct,這將導致您的請求使用:

require 'optparse' 
require 'ostruct' 

options = OpenStruct.new 
OptionParser.new do |opt| 
    opt.on('-f', '--first_name FIRSTNAME', 'The first name') { |o| options.first_name = o } 
    opt.on('-l', '--last_name LASTNAME', 'The last name') { |o| options.last_name = o } 
end.parse! 

puts options.first_name + ' ' + options.last_name 

$ ruby test.rb --first_name=foo --last_name=bar 
foo bar 

I噸甚至自動生成你的-h--help選項:

$ ruby test.rb -h 
Usage: test [options] 
     --first_name FIRSTNAME 
     --last_name LASTNAME 

您可以使用短標誌太:

require 'optparse' 

options = {} 
OptionParser.new do |opt| 
    opt.on('-f', '--first_name FIRSTNAME') { |o| options[:first_name] = o } 
    opt.on('-l', '--last_name LASTNAME') { |o| options[:last_name] = o } 
end.parse! 

puts options 

運行,通過它的步伐:

$ ruby test.rb -h 
Usage: test [options] 
    -f, --first_name FIRSTNAME 
    -l, --last_name LASTNAME 
$ ruby test.rb -f foo --l bar 
{:first_name=>"foo", :last_name=>"bar"} 

可以很容易地添加內聯解釋對於選項太:

OptionParser.new do |opt| 
    opt.on('-f', '--first_name FIRSTNAME', 'The first name') { |o| options[:first_name] = o } 
    opt.on('-l', '--last_name LASTNAME', 'The last name') { |o| options[:last_name] = o } 
end.parse! 

和:

$ ruby test.rb -h 
Usage: test [options] 
    -f, --first_name FIRSTNAME  The first name 
    -l, --last_name LASTNAME   The last name 

OptionParser還支持參數轉換成類型,如整數或數組。有關更多示例和信息,請參閱文檔。

你也應該看看相關的問題右側列表:

0

我personnaly使用Docopt。這是更清晰,可維護和閱讀。

查看示例online Ruby implementation doc。用法非常簡單。

gem install docopt 

Ruby代碼:

doc = <<DOCOPT 
My program who says hello 

Usage: 
    #{__FILE__} --first_name=<first_name> --last_name=<last_name> 
DOCOPT 

begin 
    args = Docopt::docopt(doc) 
rescue Docopt::Exit => e 
    puts e.message 
    exit 
end 

print "Hello #{args['--first_name']} #{args['--last_name']}" 

然後美其名曰:

$ ./says_hello.rb --first_name=Homer --last_name=Simpsons 
Hello Homer Simpsons 

而且不帶參數:

$ ./says_hello.rb 
Usage: 
    says_hello.rb --first_name=<first_name> --last_name=<last_name>