2014-10-17 32 views
31

Tôi muốn gọi một kịch bản lệnh Ruby từ dòng lệnh và chuyển các tham số là các cặp khóa/giá trị.Parse đối số dòng lệnh trong một tập lệnh Ruby

lệnh gọi dòng:

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

my_script.rb:

puts args.first_name + args.last_name 

Ruby cách tiêu chuẩn để làm điều này là gì? Trong các ngôn ngữ khác, tôi thường phải sử dụng một trình phân tích cú pháp tùy chọn. Trong Ruby tôi thấy chúng ta có ARGF.read, nhưng điều đó dường như không hoạt động các cặp khóa/giá trị như trong ví dụ này.

OptionParser có vẻ đầy hứa hẹn, nhưng tôi không thể biết liệu nó có thực sự hỗ trợ trường hợp này hay không.

+0

https://www.ruby-toolbox.com/categories/CLI_Option_Parsers –

+0

Nếu tôi không nhầm, Highline có vẻ l ike helper chức năng để yêu cầu người dùng cho đầu vào. Vì vậy, tôi sẽ sử dụng Highline để có giao diện điều khiển của tôi nói 'First Name: 'và chờ đợi đầu vào của họ. Có một chức năng cụ thể tôi nên nhìn vào nó? –

+1

Có nhiều loại đá quý bạn có thể chọn; trang web đó phân loại các thư viện và sắp xếp chúng theo mức độ phổ biến. Tôi thậm chí đã viết gem của riêng mình, được gọi là 'acclaim', và nó hỗ trợ cú pháp' --option = value'. Tuy nhiên, tôi không có thời gian để duy trì các dự án phần mềm miễn phí của mình. Bạn nên chọn một thư viện được hỗ trợ tốt hơn. –

Trả lời

23

Dựa trên câu trả lời của @MartinCortez, đây là một lần tắt đơn giản tạo ra một băm của các cặp khóa/giá trị, trong đó các giá trị phải được kết hợp với một dấu hiệu =. Nó cũng hỗ trợ lập luận cờ không có giá trị:

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

... hoặc cách khác ...

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

gọi với -x=foo -h --jim=jam nó trả {"x"=>"foo", "h"=>nil, "jim"=>"jam"} để bạn có thể làm những việc như:

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

Trong khi có nhiều thư viện để xử lý việc này — bao gồm GetoptLong included with Ruby —Tôi thích cá nhân tôi hơn. Dưới đây là mô hình tôi sử dụng, mà làm cho nó một cách hợp lý chung chung, không gắn với một định dạng sử dụng cụ thể, và đủ linh hoạt để cho phép cờ trộn, tùy chọn, và đối số yêu cầu trong đơn đặt hàng khác nhau: của

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

Điều này rất hay, nhưng nếu bạn cũng đang sử dụng 'ARGF' được tích hợp sẵn để đọc luồng thông qua tên tệp/stdin bạn cần đảm bảo tiêu thụ các đối số từ' ARGV' trước khi sử dụng 'ARGF.read' hoặc bạn sẽ kết thúc với lỗi "Không có tệp hoặc thư mục" như vậy. – Lauren

2

Một chút tiêu chuẩn của Ruby Regexp trong myscript.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'] 

Và trên dòng lệnh:

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

Tạo:

$ donald knuth

+1

Điều đó khá hay! Yêu cầu sử dụng '=' để phân biệt giữa '-f foo' và' -x -y bar' là một chút không khởi động để sử dụng trong thế giới thực cho tôi, nhưng nó là một hack nhanh chóng. – Phrogz

+1

Đúng. Tôi chỉ theo định dạng của anh ta ở trên. Dựa trên câu hỏi, tôi không chắc liệu anh ấy có quan tâm đến sự định hướng hay không. –

+0

Điều đó trông tuyệt vời nhưng bạn định nghĩa 's' như thế nào? 'ARGV.join.to_s'? –

72

của Ruby built-in OptionParser làm điều này độc đáo. Kết hợp nó với OpenStruct và bạn ở nhà miễn phí:

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 sẽ chứa các thông số và giá trị như một băm.

tiết kiệm và chạy mà tại dòng lệnh không có tham số kết quả trong:

$ ruby test.rb 
{} 

Chạy nó với các thông số:

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

Đó dụ đang sử dụng một Hash để chứa các tùy chọn, nhưng bạn có thể sử dụng một OpenStruct đó sẽ dẫn đến việc sử dụng như yêu cầu của bạn:

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 

tôi thậm chí không tự động tạo -h hoặc --help bạn lựa chọn:

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

Bạn có thể sử dụng lá cờ ngắn quá:

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 

Chạy rằng thông qua tốc độ của nó:

$ 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"} 

Thật dễ dàng để thêm lời giải thích inline cho các tùy chọn:

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! 

và:

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

OptionParser cũng hỗ trợ chuyển đổi các tham số để một kiểu, chẳng hạn như một Integer hoặc một mảng. Tham khảo tài liệu để biết thêm các ví dụ và thông tin.

Bạn cũng nên xem xét các câu hỏi liên quan đến danh sách quyền:

0

tôi personnaly sử dụng Docopt. Điều này là rõ ràng hơn, duy trì và đọc esay.

Hãy xem online Ruby implementation doc để biết ví dụ. Cách sử dụng thực sự đơn giản. đang

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']}" 

Sau đó gọi:

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

Và không có đối số:

$ ./says_hello.rb 
Usage: 
    says_hello.rb --first_name=<first_name> --last_name=<last_name> 
Các vấn đề liên quan