Phân tích cú pháp tùy chọn dòng lệnh thực sự rẻ trong Ruby


114

EDIT: Xin vui lòng, vui lòng , vui lòng đọc hai yêu cầu được liệt kê ở cuối bài đăng này trước khi trả lời. Mọi người tiếp tục đăng các đá quý và thư viện mới của họ và những thứ khác, rõ ràng là không đáp ứng yêu cầu.

Đôi khi tôi muốn hack một số tùy chọn dòng lệnh thành một tập lệnh đơn giản với giá rất rẻ. Một cách thú vị để làm điều đó, mà không cần xử lý getopts hoặc phân tích cú pháp hoặc bất cứ điều gì tương tự, là:

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

Nó không hoàn toàn là cú pháp tùy chọn Unix bình thường, vì nó sẽ chấp nhận các tham số dòng lệnh không phải tùy chọn tùy chọn, như trong " myprog -i foo bar -q", nhưng tôi có thể sống với điều đó. (Một số người, chẳng hạn như các nhà phát triển Subversion, thích điều này. Đôi khi tôi cũng vậy.)

Một tùy chọn chỉ có mặt hoặc không có mặt không thể được thực hiện đơn giản hơn nhiều so với tùy chọn trên. (Một phép gán, một lệnh gọi hàm, một tác dụng phụ.) Có cách nào đơn giản như nhau để xử lý các tùy chọn có tham số, chẳng hạn như " -f tên tệp " không?

BIÊN TẬP:

Một điểm mà tôi đã không nói trước đó, bởi vì điều đó đã không trở nên rõ ràng với tôi cho đến khi tác giả của Trollop đề cập rằng thư viện vừa vặn "trong một tệp [800 dòng]", đó là tôi không chỉ tìm kiếm sự sạch sẽ cú pháp, nhưng đối với một kỹ thuật có các đặc điểm sau:

  1. Toàn bộ mã có thể được bao gồm trong tệp kịch bản (mà không lấn át bản thân tập lệnh thực, có thể chỉ vài chục dòng), vì vậy người ta có thể thả một tệp duy nhất trong một bindir trên bất kỳ hệ thống nào có Ruby 1.8 tiêu chuẩn [5-7] cài đặt và sử dụng nó. Nếu bạn không thể viết một tập lệnh Ruby không có câu lệnh yêu cầu và mã để phân tích cú pháp một vài tùy chọn dưới hàng chục dòng hoặc lâu hơn, bạn không đạt yêu cầu này.

  2. Mã này đủ nhỏ và đơn giản để người ta có thể nhớ đủ để nhập trực tiếp mã sẽ thực hiện thủ thuật, thay vì cắt và dán từ một nơi khác. Hãy nghĩ đến tình huống bạn đang ở trên bảng điều khiển của một máy chủ có tường lửa không có quyền truy cập Internet và bạn muốn tập hợp một tập lệnh nhanh để khách hàng sử dụng. Tôi không biết bạn thế nào, nhưng (ngoài việc không đạt yêu cầu ở trên) thì việc ghi nhớ ngay cả 45 dòng của bảng mã vi mô đơn giản không phải là điều tôi quan tâm.


2
Chỉ tò mò về sự phản đối chống lại getoptlong?
Mark Carey

Độ dài của nó. Với getoptlog, đôi khi mã phân tích cú pháp tùy chọn dài hơn một phần của tập lệnh thực sự hoạt động. Đây không chỉ là vấn đề thẩm mỹ mà còn là vấn đề chi phí bảo trì.
cjs

8
Tôi không hiểu yêu cầu bao gồm tập lệnh - cả hai getoptlongoptparseđều nằm trong thư viện ruby ​​chuẩn, vì vậy bạn không CẦN sao chép chúng khi triển khai tập lệnh của mình - nếu ruby ​​hoạt động trên máy đó thì require 'optparse'hoặc require 'getoptlong'cũng sẽ hoạt động.
rapter

Xem stackoverflow.com/questions/21357953/… , cũng như câu trả lời của William Morgan bên dưới về Trollop.
Fearless_fool

@CurtSampson Tôi không tin có bao nhiêu người không trả lời câu hỏi của bạn. Dù bằng cách nào, cuối cùng nhận được một câu trả lời tốt khoảng 3 bài viết xuống XD XD
OneChillDude

Câu trả lời:


235

Là tác giả của Trollop , tôi không thể TIN những thứ mà mọi người cho là hợp lý trong một trình phân tích cú pháp tùy chọn. Nghiêm túc. Nó làm rối trí tâm trí.

Tại sao tôi phải tạo một mô-đun mở rộng một số mô-đun khác để phân tích cú pháp các tùy chọn? Tại sao tôi phải phân lớp bất cứ thứ gì? Tại sao tôi phải đăng ký một số "khuôn khổ" chỉ để phân tích cú pháp dòng lệnh?

Đây là phiên bản Trollop ở trên:

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

Và đó là nó. optsbây giờ là một băm với các phím :quiet, :interactive, và :filename. Bạn có thể làm bất cứ điều gì bạn muốn với nó. Và bạn sẽ nhận được một trang trợ giúp tuyệt đẹp, được định dạng để vừa với chiều rộng màn hình của bạn, tên đối số ngắn tự động, kiểm tra kiểu ... mọi thứ bạn cần.

Đó là một tệp, vì vậy bạn có thể thả nó vào thư mục lib / của mình nếu bạn không muốn có sự phụ thuộc chính thức. Nó có một DSL tối thiểu, dễ sử dụng.

LOC cho mỗi người tùy chọn. Nó quan trọng.


39
BTW, +1 vì đã viết Trollop (đã được đề cập ở đây), nhưng vui lòng giảm bớt đoạn đầu tiên một chút.
cjs

33
Tôi e rằng anh ấy có quyền khiếu nại trong trường hợp này. Khi bạn xem xét các lựa chọn thay thế: [1 ] [2 ] [3 ], về cơ bản chỉ xử lý một mảng chuỗi đơn giản (không thực sự, hãy để điều đó chìm vào), bạn không thể không tự hỏi TẠI SAO? Bạn thu được gì từ tất cả những thứ đó? Đây không phải là C, nơi các chuỗi, "có vấn đề". Tất nhiên là của riêng mình. :)
srcspider

50
Xin đừng làm điều này xuống một chút. Đó là một nền kinh tế chính đáng, anh em.
William Pietri

7
Hãy giảm âm từ thứ mười xuống một chút.
Andrew Grimm

3
+1 cho Trollop. Tôi sử dụng nó cho hệ thống tự động hóa thử nghiệm của mình và nó chỉ hoạt động. Thêm vào đó, việc viết mã rất dễ dàng nên đôi khi tôi sắp xếp lại biểu ngữ của mình chỉ để trải nghiệm niềm vui của nó.
kinofrost

76

Tôi chia sẻ sự chán ghét của bạn require 'getopts', chủ yếu là do sự tuyệt vời đó là 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}

6
Cảm ơn, tôi không thể nhìn thấy cách này không phù hợp với yêu cầu Ops, đặc biệt là xem xét nó tất cả trong lib tiêu chuẩn, so với nhu cầu của việc cài đặt / vendoring bất kỳ mã phi tiêu chuẩn
dolzenko

3
phiên bản này trông giống như phiên bản quân đội ngoại trừ nó không cần tệp bổ sung.
Claudiu

59

Đây là kỹ thuật tiêu chuẩn tôi thường sử dụng:

#!/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}")

3
Trả lời câu hỏi, nhưng anh bạn, Trollop dường như dễ đối phó hơn rất nhiều. Tại sao phải phát minh lại bánh xe khi bánh xe được làm trước trơn hơn rất nhiều?
Mikey TK

7
Bánh xe làm sẵn không mượt mà hơn. Đọc kỹ lại câu hỏi, chú ý các yêu cầu.
cjs 3/12/12

2
+1 Đôi khi bạn cần phải phát minh lại bánh xe, vì bạn không muốn hoặc đơn giản là không thể sử dụng các phụ thuộc khác như Trollop.
lzap

Trollop không cần cài đặt như một viên ngọc. Bạn chỉ cần thả một tệp vào libthư mục hoặc mã của mình và sử dụng nó mà không cần chạm vào rubygem.
Overbryd

Đối với tôi, tôi đã phải thay đổi when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")để when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")hoặc nó sẽ nhận được vào một vòng lặp vô hạn sử dụng
casey

36

Vì không ai xuất hiện để đề cập đến nó, và tiêu đề không đề cập đến giá rẻ dòng lệnh phân tích, tại sao không chỉ để cho Ruby thông dịch viên làm việc cho bạn? Nếu bạn vượt qua công -stắc (ví dụ: trong shebang của bạn), bạn sẽ nhận được công tắc đơn giản miễn phí, được gán cho các biến toàn cục gồm một chữ cái. Đây là ví dụ của bạn sử dụng công tắc đó:

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

Và đây là kết quả đầu ra khi tôi lưu nó dưới dạng ./testvà chmod nó +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"]

Xem ruby -h chi tiết.

Điều đó phải là rẻ như nó có được. Nó sẽ tăng NameError nếu bạn thử chuyển đổi như -:vậy, vì vậy có một số xác nhận ở đó. Tất nhiên, bạn không thể có bất kỳ công tắc nào sau một đối số không chuyển đổi, nhưng nếu bạn cần thứ gì đó lạ mắt, bạn thực sự nên sử dụng OptionParser tối thiểu. Trên thực tế, điều duy nhất làm tôi khó chịu về kỹ thuật này là bạn sẽ nhận được cảnh báo (nếu bạn đã bật chúng) khi truy cập vào một biến toàn cục chưa được đặt, nhưng nó vẫn sai, vì vậy nó chỉ hoạt động tốt với các công cụ bỏ đi và nhanh chóng tập lệnh.

Một lưu ý được FelipeC chỉ ra trong các nhận xét trong " Cách phân tích cú pháp tùy chọn dòng lệnh thực sự rẻ trong Ruby ", đó là trình bao của bạn có thể không hỗ trợ shebang 3 mã thông báo; bạn có thể cần phải thay thế /usr/bin/env ruby -wbằng đường dẫn thực tế đến ruby ​​của mình (như /usr/local/bin/ruby -w) hoặc chạy nó từ một tập lệnh trình bao bọc hoặc một cái gì đó.


2
Cảm ơn :) Tôi chắc chắn hy vọng anh ấy đã không chờ đợi câu trả lời này trong hai năm qua.
DarkHeart

3
Tôi thực sự đã chờ đợi câu trả lời này trong hai năm qua. :-) Nghiêm trọng hơn, đây là kiểu suy nghĩ thông minh mà tôi đang tìm kiếm. Điều cảnh báo là một chút khó chịu, nhưng tôi có thể nghĩ ra cách để giảm thiểu điều đó.
cjs

Rất vui vì tôi có thể (cuối cùng) giúp đỡ, @CurtSampson, Các cờ của MRI được tách thẳng ra khỏi Perl, nơi chúng có xu hướng được sử dụng vô cớ trong các lớp lót một lớp vỏ. Hãy chấp nhận, nếu câu trả lời vẫn hữu ích cho bạn. :)
bjjb

1
Bạn không thể sử dụng nhiều đối số trong một tập hợp trong Linux. / usr / bin / env: 'ruby -s': Không có tập tin hoặc thư mục
FelipeC

13

Tôi đã tạo micro-optparse để đáp ứng nhu cầu rõ ràng này về một trình phân tích cú pháp tùy chọn ngắn, nhưng dễ sử dụng. Nó có cú pháp tương tự như Trollop và ngắn 70 dòng. Nếu bạn không cần xác nhận và có thể làm mà không có dòng trống, bạn có thể cắt nó xuống còn 45 dòng. Tôi nghĩ đó chính xác là những gì bạn đang tìm kiếm.

Ví dụ ngắn gọn:

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!

Gọi script bằng -hhoặc --helpsẽ in

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

Nó kiểm tra xem đầu vào có cùng loại với giá trị mặc định hay không, tạo bộ truy cập ngắn và dài, in thông báo lỗi mô tả nếu các đối số không hợp lệ được đưa ra và hơn thế nữa.

Tôi đã so sánh một số trình phân tích cú pháp tùy chọn bằng cách sử dụng từng trình phân tích cú pháp tùy chọn cho vấn đề tôi gặp phải. Bạn có thể sử dụng các ví dụ này và bản tóm tắt của tôi để đưa ra quyết định mang tính thông tin. Vui lòng thêm nhiều triển khai vào danh sách. :)


Thư viện chính nó trông giống như nó có thể là tuyệt vời. Tuy nhiên, việc so sánh số lượng dòng với Trollop không phải là điều khó khăn vì bạn phụ thuộc và yêu cầu optparse(cho hoặc nhận) 1937 dòng.
Telemachus

6
So sánh số lượng dòng là hoàn toàn OK, vì đây optparselà một thư viện mặc định, tức là nó đi kèm với mọi cài đặt ruby. Trolloplà thư viện của bên thứ ba, do đó bạn phải nhập mã hoàn chỉnh mỗi khi bạn muốn đưa nó vào một dự án. µ-optparse luôn chỉ yêu cầu ~ 70 dòng, vì optparseđã có sẵn.
Florian Pilz

8

Tôi hoàn toàn hiểu tại sao bạn muốn tránh optparse - nó có thể bị quá nhiều. Nhưng có một số giải pháp "nhẹ" hơn nhiều (so với OptParse) đi kèm dưới dạng thư viện nhưng đủ đơn giản để làm cho một cài đặt gem đơn lẻ trở nên đáng giá.

Ví dụ: hãy xem ví dụ OptiFlag này . Chỉ cần một vài dòng để xử lý. Một ví dụ hơi cắt ngắn phù hợp với trường hợp của bạn:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

Có rất nhiều ví dụ tùy chỉnh quá . Tôi nhớ lại việc sử dụng một cái khác thậm chí còn dễ dàng hơn, nhưng nó đã thoát khỏi tôi bây giờ nhưng tôi sẽ quay lại và thêm nhận xét ở đây nếu tôi tìm thấy nó.


Vui lòng chỉnh sửa câu trả lời của bạn để phù hợp hơn với câu hỏi được làm rõ.
cjs

4

Đây là những gì tôi sử dụng cho các args thực sự, rất rẻ:

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

main

vì vậy nếu bạn chạy programname foo barnó sẽ gọi foo và sau đó là thanh. Nó rất tiện lợi cho các tập lệnh vứt bỏ.


3

Bạn có thể thử một cái gì đó như:

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

3

Bạn đã từng coi Thor bằng wycats chưa? Tôi nghĩ nó sạch hơn rất nhiều so với optparse. Nếu bạn đã viết một tập lệnh, có thể sẽ tốn thêm một số công việc để định dạng nó hoặc cấu trúc lại nó cho thor, nhưng nó làm cho các tùy chọn xử lý rất đơn giản.

Đây là đoạn mã ví dụ từ README:

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 tự động lập bản đồ các lệnh như:

app install myname --force

Điều đó được chuyển đổi thành:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Kế thừa từ Thor để biến một lớp học thành một người lập bản đồ tùy chọn
  2. Ánh xạ các số nhận dạng không hợp lệ bổ sung với các phương pháp cụ thể. Trong trường hợp này, hãy chuyển đổi -L thành: list
  3. Mô tả phương pháp ngay bên dưới. Tham số đầu tiên là thông tin sử dụng và tham số thứ hai là mô tả.
  4. Cung cấp bất kỳ tùy chọn bổ sung nào. Chúng sẽ được điều chỉnh từ - và - tham số. Trong trường hợp này, tùy chọn --force và a -f được thêm vào.

Tôi thích thứ ánh xạ lệnh, vì một nhị phân duy nhất với một loạt lệnh con là điều tôi thường làm. Tuy nhiên, mặc dù bạn đã đi một cách từ 'ánh sáng'. Bạn có thể tìm thấy một cách đơn giản hơn để thể hiện cùng một chức năng không? Điều gì xảy ra nếu bạn không cần in --helpđầu ra? Điều gì sẽ xảy ra nếu "head myprogram.rb" là đầu ra trợ giúp?
cjs

3

Đây là trình phân tích cú pháp tùy chọn nhanh và bẩn yêu thích của tôi:

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

Các tùy chọn là biểu thức chính quy, vì vậy "-h" cũng sẽ khớp với "--help".

Dễ đọc, dễ nhớ, không có thư viện bên ngoài và mã tối thiểu.


Vâng, nó sẽ. Nếu đó là một vấn đề bạn có thể thêm regex hơn, ví dụ/-h(\b|elp)
EdwardTeach

2

Trollop là khá rẻ.


Đó sẽ là, < trollop.rubyforge.org >. Tôi nghĩ tôi thích nó hơn, mặc dù tôi thực sự không tìm kiếm một thư viện.
cjs

Đúng, nó là một thư viện. Tuy nhiên, ở mức <800 LOC, đó là một mức khá không đáng kể. gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
g33kz0r

1
Tôi đã nghĩ rằng có lẽ 30-50 dòng là tốt, nếu tôi đi xa đến mức sử dụng một "thư viện". Nhưng một lần nữa, tôi đoán khi bạn đã có một tệp riêng biệt chứa đầy mã, thiết kế API quan trọng hơn số dòng. Tuy nhiên, tôi không chắc mình có muốn đưa nó vào tập lệnh một lần mà tôi chỉ muốn đưa vào thư mục bin trên một hệ thống ngẫu nhiên.
cjs

1
Bạn đã hiểu ngược lại: vấn đề là tránh phải có một chiến lược triển khai phức tạp hơn.
cjs

1
Bạn hoàn toàn sai, bởi vì bạn đang hiểu sai hoàn toàn (hoặc bỏ qua) nhu cầu và ý định của người đặt câu hỏi. Tôi đề nghị bạn đọc lại câu hỏi một cách cẩn thận, đặc biệt là hai điểm cuối cùng.
cjs

2

Nếu bạn muốn phân tích cú pháp dòng lệnh đơn giản cho các lệnh khóa / giá trị mà không cần sử dụng đá quý:

Nhưng điều này chỉ hoạt động nếu bạn luôn có các cặp khóa / giá trị.

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

Nếu bạn không cần kiểm tra, bạn chỉ có thể sử dụng:

opts = {} 

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

2

Đây là đoạn mã tôi sử dụng ở đầu hầu hết các tập lệnh của mình:

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) }
}

Tôi cũng ghét yêu cầu các tệp bổ sung trong các tập lệnh nhanh và bẩn của mình. Giải pháp của tôi gần giống như những gì bạn đang yêu cầu. Tôi dán một đoạn mã 10 dòng vào đầu bất kỳ tập lệnh nào của tôi để phân tích cú pháp dòng lệnh và gắn các args vị trí và chuyển thành một đối tượng Hash (thường được gán cho một đối tượng mà tôi đã đặt tên là arghash trong các ví dụ bên dưới).

Đây là một dòng lệnh mẫu mà bạn có thể muốn phân tích cú pháp ...

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

Mà sẽ trở thành một Hash như thế này.

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

Ngoài ra, hai phương pháp tiện lợi được thêm vào Hash:

  • argc() sẽ trả về số lượng các đối số không chuyển đổi.
  • switches() sẽ trả về một mảng chứa các khóa cho các thiết bị chuyển mạch hiện có

Điều này có nghĩa là để cho phép một số thứ nhanh chóng và bẩn thỉu như ...

  • Xác thực Tôi có số lượng đối số vị trí phù hợp bất kể các công tắc được chuyển vào ( arghash.argc == 2)
  • Truy cập các đối số vị trí theo vị trí tương đối của chúng, bất kể các công tắc xuất hiện trước hoặc xen kẽ với các đối số vị trí (ví dụ: arghash[1]luôn nhận được đối số không phải công tắc thứ hai).
  • Hỗ trợ các công tắc được gán giá trị trong dòng lệnh, chẳng hạn như "--max = 15" có thể được truy cập bằng cách arghash['--max=']đó mang lại giá trị '15' cho dòng lệnh ví dụ.
  • Kiểm tra sự hiện diện hay vắng mặt của một công tắc trong dòng lệnh bằng cách sử dụng một ký hiệu rất đơn giản, chẳng hạn như ký hiệu arghash['-s']đánh giá là true nếu nó có mặt và nil nếu không có.
  • Kiểm tra sự hiện diện của một công tắc hoặc các lựa chọn thay thế của công tắc bằng cách sử dụng các hoạt động thiết lập như

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

  • Xác định việc sử dụng các công tắc không hợp lệ bằng cách sử dụng các hoạt động thiết lập như

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

  • Chỉ định giá trị mặc định cho các đối số bị thiếu bằng cách sử dụng một Hash.merge()ví dụ đơn giản, chẳng hạn như ví dụ dưới đây để điền giá trị cho -max = nếu một đối số không được đặt và thêm đối số vị trí thứ 4 nếu một đối số không được chuyển.

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


(Tôi đã chỉnh sửa điều này để cải thiện định dạng mã, chủ yếu sử dụng căn chỉnh để làm cho cấu trúc khối và điều khiển rõ ràng hơn, điều mà tôi cảm thấy đặc biệt quan trọng trong một thứ dày đặc dấu chấm câu như thế này. Nhưng nếu bạn không thích định dạng mới, vui lòng để hoàn tác chỉnh sửa.)
cjs 14/07/18

Điều này khá hay, nếu không phải là thứ dễ đọc nhất trên đời. Tôi thích rằng nó cũng chứng minh rằng việc thay đổi "cú pháp" đối số (ở đây, cho phép các tùy chọn sau các đối số vị trí và không cho phép các đối số tùy chọn ngoại trừ bằng cách sử dụng =) có thể tạo ra sự khác biệt trong mã bạn cần.
cjs 14/07/18

Cảm ơn vì đã định dạng lại. Nó chắc chắn khó đọc và người ta có thể dễ dàng giao dịch độ dài của mã cho rõ ràng. Bây giờ tôi tin tưởng mã này, ít nhiều, tôi coi nó như một viên ngọc quý và tôi không bao giờ cố gắng tìm hiểu xem nó đang làm gì dưới vỏ bọc (vì vậy sự rõ ràng không còn quan trọng nữa khi tôi tin tưởng).
David Foster

1

Điều này rất giống với câu trả lời được chấp nhận, nhưng sử dụng ARGV.delete_ifđó là những gì tôi sử dụng trong trình phân tích cú pháp đơn giản của mình . Sự khác biệt thực sự duy nhất là các tùy chọn với các đối số phải cùng nhau (ví dụ -l=file).

def usage
  "usage: #{File.basename($0)}: [-l=<logfile>] [-q] file ..."
end

$quiet = false
$logfile = nil

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

puts "quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}"

0

Rõ ràng @WilliamMorgan và tôi nghĩ giống nhau. Tôi vừa phát hành tối qua lên Github, những gì tôi thấy bây giờ là một thư viện tương tự như Trollop (Được đặt tên như thế nào?) Sau khi thực hiện tìm kiếm OptionParser trên Github, hãy xem Chuyển đổi

Có một vài điểm khác biệt, nhưng triết lý thì giống nhau. Một điểm khác biệt rõ ràng là Switch phụ thuộc vào OptionParser.


0

Tôi đang phát triển viên ngọc phân tích cú pháp tùy chọn của riêng mình có tên là Acclaim .

Tôi viết nó vì tôi muốn tạo giao diện dòng lệnh kiểu git và có thể phân tách rõ ràng chức năng của từng lệnh thành các lớp riêng biệt, nhưng nó cũng có thể được sử dụng mà không cần toàn bộ khung lệnh:

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

Chưa có bản phát hành ổn định nào, nhưng tôi đã triển khai một số tính năng như:

  • trình phân tích cú pháp tùy chọn tùy chỉnh
  • phân tích cú pháp linh hoạt các đối số của tùy chọn cho phép cả tối thiểu và tùy chọn
  • hỗ trợ nhiều kiểu tùy chọn
  • thay thế, nối thêm hoặc nâng cao trên nhiều trường hợp của cùng một tùy chọn
  • trình xử lý tùy chọn tùy chỉnh
  • trình xử lý loại tùy chỉnh
  • các trình xử lý được xác định trước cho các lớp thư viện tiêu chuẩn chung

Có rất nhiều sự nhấn mạnh vào các lệnh vì vậy nó có thể hơi nặng đối với việc phân tích cú pháp dòng lệnh đơn giản nhưng nó hoạt động tốt và tôi đã sử dụng nó trong tất cả các dự án của mình. Nếu bạn quan tâm đến khía cạnh giao diện lệnh, hãy xem trang GitHub của dự án để biết thêm thông tin và ví dụ.


1
Tôi thực sự khuyên bạn nên Acclaim. Nó rất dễ sử dụng và có tất cả các tùy chọn bạn cần.
bowsersenior,

0

Giả sử một lệnh có nhiều nhất một hành động và số lượng tùy chọn tùy ý như sau:

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

Phân tích cú pháp mà không xác nhận có thể như thế này:

ACTION = ARGV.shift
OPTIONS = ARGV.join.tr('-', '')

if ACTION == '***'
  ...
  if OPTIONS.include? '*'
    ...
  end
  ...
end

0

https://github.com/soveran/clap

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

46LOC (ở mức 1.0.0), không phụ thuộc vào trình phân tích cú pháp tùy chọn bên ngoài. Hoàn thành công việc. Có lẽ không đầy đủ tính năng như những người khác, nhưng đó là 46LOC.

Nếu bạn kiểm tra mã, bạn có thể dễ dàng sao chép kỹ thuật cơ bản - gán lambdas và sử dụng tính năng hiếm có để đảm bảo số lượng args phù hợp theo cờ nếu bạn thực sự không muốn có một thư viện bên ngoài.

Đơn giản. Rẻ.


CHỈNH SỬA : khái niệm cơ bản đã sôi sục vì tôi cho rằng bạn có thể sao chép / dán nó vào một tập lệnh để tạo một trình phân tích cú pháp dòng lệnh hợp lý. Đó chắc chắn không phải là điều tôi muốn ghi nhớ, nhưng sử dụng lambda arity làm trình phân tích cú pháp giá rẻ là một ý tưởng mới lạ:

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...

Vui lòng đọc điểm 1 ở cuối câu hỏi. Nếu bạn không thể nhập tất cả mã cần thiết ngay trong câu trả lời của mình ở đây, thì đó không phải là câu trả lời cho câu hỏi.
cjs 14/07/18

Điểm tốt! Tôi nghĩ vào thời điểm đó, tôi cho rằng lib đủ nhỏ để bạn có thể sao chép / dán toàn bộ vào bất kỳ tập lệnh nào bạn đang làm việc mà không cần phụ thuộc bên ngoài, nhưng nó chắc chắn không phải là một lớp lót sạch mà tôi sẽ cam kết với bộ nhớ để hoàn thành quan điểm số 2 của bạn. Tôi nghĩ rằng khái niệm cơ bản là mới lạ và đủ thú vị để tôi đã tiếp tục và tạo ra một phiên bản tổng hợp trả lời câu hỏi của bạn phù hợp hơn một chút.
Ben Alavi

-1

Tôi sẽ chia sẻ trình phân tích cú pháp tùy chọn đơn giản của riêng tôi mà tôi đã làm việc trong một thời gian. Nó chỉ đơn thuần là 74 dòng mã và nó thực hiện những điều cơ bản về những gì trình phân tích cú pháp tùy chọn nội bộ của Git thực hiện. Tôi lấy OptionParser làm nguồn cảm hứng và cả Git.

https://gist.github.com/felipec/6772110

Nó trông như thế này:

opts = ParseOpt.new
opts.usage = "git foo"

opts.on("b", "bool", help: "Boolean") do |v|
 $bool = v
end

opts.on("s", "string", help: "String") do |v|
 $str = v
end

opts.on("n", "number", help: "Number") do |v|
 $num = v.to_i
end

opts.parse

Bạn thậm chí đã không kiểm tra mã. Tôi đã đưa ra một câu trả lời khác lấy mã phân tích cú pháp.
FelipeC

Tôi không cần phải cho rằng bạn đã nói rằng nó dài 74 dòng. Tuy nhiên, bây giờ tôi mới xem xét nó và nó vẫn vi phạm câu đầu tiên của yêu cầu 2. (Câu trả lời này cũng vi phạm quy ước Stack Overflow rằng bạn nên bao gồm mã trong câu trả lời của mình thay vì đưa ra một liên kết ngoài trang web.)
cjs

-1

EasyOptions hoàn toàn không yêu cầu bất kỳ mã phân tích tùy chọn nào. Chỉ cần viết văn bản trợ giúp, yêu cầu, xong.

## Options:
##   -i, --interactive  Interactive mode
##   -q, --quiet        Silent mode

require 'easyoptions'
unless EasyOptions.options[:quiet]
    puts 'Interactive mode enabled' if EasyOptions.options[:interactive]
    EasyOptions.arguments.each { |item| puts "Argument: #{item}" }
end

EasyOptions là một tệp Ruby duy nhất không có câu lệnh yêu cầu và không có mã phân tích cú pháp nào cần nhớ. Có vẻ như bạn muốn một cái gì đó có thể nhúng đủ mạnh nhưng đơn giản để nhớ.
Renato Silva
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.