ตัวเลือกบรรทัดคำสั่งราคาถูกจริงๆการแยกวิเคราะห์ใน Ruby


114

แก้ไข: กรุณาโปรด , โปรดอ่านสองความต้องการที่ด้านล่างของโพสต์นี้ก่อนที่จะตอบ ผู้คนมักจะโพสต์อัญมณีและไลบรารีใหม่ของตนและสิ่งที่ไม่เป็นไปตามข้อกำหนดอย่างชัดเจน

บางครั้งฉันต้องการแฮ็กตัวเลือกบรรทัดคำสั่งบางตัวในสคริปต์ง่ายๆ วิธีที่สนุกโดยไม่ต้องจัดการกับ getopts หรือการแยกวิเคราะห์หรืออะไรทำนองนั้นคือ:

...
$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" แต่ฉันสามารถอยู่กับสิ่งนั้นได้ (บางคนเช่นนักพัฒนาที่ถูกโค่นล้มชอบสิ่งนี้บางครั้งฉันก็ทำเกินไป)

ตัวเลือกที่มีอยู่หรือไม่มีอยู่ไม่สามารถนำไปใช้งานได้ง่ายกว่าที่กล่าวมา (การกำหนดหนึ่งการเรียกใช้ฟังก์ชันหนึ่งผลข้างเคียง) มีวิธีที่ง่ายพอ ๆ กันในการจัดการกับตัวเลือกที่ใช้พารามิเตอร์เช่น " -f ชื่อไฟล์ " หรือไม่

แก้ไข:

จุดหนึ่งที่ฉันไม่ได้ทำไว้ก่อนหน้านี้เพราะมันไม่ชัดเจนสำหรับฉันจนกระทั่งผู้เขียน Trollop กล่าวว่าไลบรารีพอดี "ในไฟล์ [800 บรรทัด] เดียว" คือฉันไม่เพียง แต่ต้องการความสะอาดเท่านั้น ไวยากรณ์ แต่สำหรับเทคนิคที่มีลักษณะดังต่อไปนี้:

  1. รหัสทั้งหมดสามารถรวมอยู่ในไฟล์สคริปต์ (โดยไม่ต้องใช้สคริปต์จริงซึ่งอาจมีเพียงสองสามบรรทัด) เพื่อให้สามารถวางไฟล์เดียวในbindir บนระบบใดก็ได้ที่มี Ruby 1.8 มาตรฐาน . [5-7] การติดตั้งและใช้งาน หากคุณไม่สามารถเขียนสคริปต์ Ruby ที่ไม่มีคำสั่งที่ต้องการและในกรณีที่รหัสสำหรับแยกวิเคราะห์ตัวเลือกสองสามตัวเลือกนั้นอยู่ภายใต้บรรทัดไม่เกินหนึ่งโหลแสดงว่าคุณทำตามข้อกำหนดนี้ไม่สำเร็จ

  2. โค้ดมีขนาดเล็กและเรียบง่ายพอที่จะจำได้มากพอที่จะพิมพ์โค้ดโดยตรงที่จะทำเคล็ดลับแทนที่จะตัดและวางจากที่อื่น ลองนึกถึงสถานการณ์ที่คุณอยู่บนคอนโซลของเซิร์ฟเวอร์ที่ติดไฟและไม่สามารถเข้าถึงอินเทอร์เน็ตได้และคุณต้องการรวบรวมสคริปต์ด่วนเพื่อให้ไคลเอ็นต์ใช้ ฉันไม่รู้เกี่ยวกับคุณ แต่ (นอกเหนือจากการทำตามข้อกำหนดข้างต้นไม่สำเร็จ) การจำแม้กระทั่ง 45 บรรทัดของ micro-optparse อย่างง่ายก็ไม่ใช่สิ่งที่ฉันสนใจที่จะทำ


2
แค่อยากรู้อยากเห็นกับการคัดค้าน getoptlong?
Mark Carey

ฟุ่มเฟื่อยของมัน ด้วย getoptlog บางครั้งตัวเลือกในการแยกวิเคราะห์โค้ดจะยาวกว่าส่วนของสคริปต์ที่ใช้งานได้จริง นี่ไม่ใช่แค่ปัญหาด้านสุนทรียศาสตร์ แต่เป็นปัญหาด้านค่าบำรุงรักษา
cjs

8
ฉันไม่เข้าใจข้อกำหนดการรวมสคริปต์ - ทั้งสองอย่างgetoptlongและoptparseอยู่ในไลบรารีทับทิมมาตรฐานดังนั้นคุณจึงไม่จำเป็นต้องคัดลอกเมื่อปรับใช้สคริปต์ของคุณ - หากทับทิมทำงานบนเครื่องนั้นrequire 'optparse'หรือrequire 'getoptlong'จะทำงานด้วย
rampion

ดูstackoverflow.com/questions/21357953/…รวมทั้งคำตอบของ William Morgan ด้านล่างเกี่ยวกับ Trollop
Fearless_fool

@CurtSampson ฉันไม่อยากจะเชื่อเลยว่ามีกี่คนที่ไม่ตอบคำถามของคุณ ไม่ว่าจะด้วยวิธีใดในที่สุดก็ได้รับคำตอบที่ดีเกี่ยวกับ 3 โพสต์ที่ลง XD XD
OneChillDude

คำตอบ:


236

ในฐานะผู้เขียนTrollopฉันไม่สามารถเชื่อได้ว่าสิ่งที่ผู้คนคิดว่าสมเหตุสมผลในตัวแยกวิเคราะห์ตัวเลือก อย่างจริงจัง. มันทำให้จิตใจสับสน

เหตุใดฉันจึงต้องสร้างโมดูลที่ขยายโมดูลอื่น ๆ เพื่อแยกวิเคราะห์ตัวเลือก ทำไมต้อง subclass อะไร? เหตุใดฉันจึงต้องสมัคร "เฟรมเวิร์ก" เพียงเพื่อแยกวิเคราะห์บรรทัดคำสั่ง

นี่คือรุ่น 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 / directory ได้หากคุณไม่ต้องการการพึ่งพาอย่างเป็นทางการ มี DSL ขั้นต่ำที่ง่ายต่อการหยิบ

LOC ต่อตัวเลือกคน มันสำคัญ


39
BTW, +1 สำหรับการเขียน Trollop (ซึ่งได้กล่าวไว้แล้วที่นี่) แต่อย่าลังเลที่จะลดทอนย่อหน้าแรกลงเล็กน้อย
cjs

33
เขามีสิทธิที่จะบ่นในกรณีนี้ฉันกลัว เมื่อคุณดูทางเลือกอื่น: [1 ] [2 ] [3 ] โดยพื้นฐานแล้วสิ่งที่เป็นเพียงการประมวลผลอาร์เรย์สตริงธรรมดา ๆ (ไม่จริงให้มันจมลงไป) คุณอดสงสัยไม่ได้ว่าทำไม คุณได้อะไรจากการขยายตัวทั้งหมด? นี่ไม่ใช่ C ซึ่งเป็นสตริง "มีปัญหา" แน่นอนสำหรับเขาแต่ละคน :)
srcspider

50
โปรดอย่าลดเสียงลงเล็กน้อย เป็นการพูดนานน่าเบื่อที่ชอบธรรมพี่ชาย
William Pietri

7
อย่าลังเลที่จะลดทอนคำที่สิบลงเล็กน้อย
Andrew Grimm

3
+1 สำหรับ Trollop ฉันใช้มันสำหรับระบบทดสอบอัตโนมัติของฉันและมันก็ใช้ได้ นอกจากนี้ยังง่ายมากที่จะเขียนโค้ดด้วยบางครั้งฉันก็จัดเรียงแบนเนอร์ใหม่เพื่อสัมผัสกับความสุขของมัน
kinofrost

76

ฉันแบ่งปันความไม่พอใจของคุณ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}

6
ขอบคุณฉันไม่เห็นว่าสิ่งนี้ไม่เหมาะกับคำขอ OPs โดยเฉพาะอย่างยิ่งเมื่อพิจารณาจากทั้งหมดใน lib มาตรฐานเมื่อเทียบกับความต้องการในการติดตั้ง / จำหน่ายรหัสที่ไม่ได้มาตรฐาน
dolzenko

3
ดูเหมือนว่าเวอร์ชัน trollop ยกเว้นว่าไม่ต้องการไฟล์พิเศษ
Claudiu

59

นี่คือเทคนิคมาตรฐานที่ฉันมักใช้:

#!/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
ตอบคำถาม แต่ผู้ชาย Trollop ดูเหมือนจะจัดการได้ง่ายกว่ามาก เหตุใดจึงต้องคิดค้นล้อใหม่ในเมื่อล้อสำเร็จรูปจึงนุ่มนวลกว่ามาก?
กี้ TK

7
ล้อสำเร็จรูปไม่เรียบกว่า อ่านคำถามอีกครั้งอย่างละเอียดโดยให้ความสำคัญกับข้อกำหนด
cjs

2
+1 บางครั้งคุณต้องสร้างวงล้อใหม่เพราะคุณไม่ต้องการหรือไม่สามารถใช้การอ้างอิงอื่น ๆ เช่น Trollop
lzap

Trollop ไม่จำเป็นต้องติดตั้งเป็นอัญมณี คุณสามารถวางไฟล์ลงในlibโฟลเดอร์หรือโค้ดของคุณและใช้งานได้โดยไม่ต้องแตะทับทิม
Overbryd

สำหรับฉันฉันมีการเปลี่ยนแปลงwhen /^-/ then usage("Unknown option: #{ARGV[0].inspect}")ไปwhen /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")หรือว่ามันจะได้รับในการใช้งานอนันต์ห่วง
เคซี่ย์

36

เนื่องจากไม่มีใครพูดถึงมันและชื่อเรื่องนี้อ้างถึงการแยกวิเคราะห์บรรทัดคำสั่งราคาถูกทำไมไม่ปล่อยให้ล่าม 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 ชี้ให้เห็นในความคิดเห็นใน " วิธีการแยกวิเคราะห์ตัวเลือกบรรทัดคำสั่งราคาถูกจริงๆใน Ruby " คือเชลล์ของคุณอาจไม่รองรับเชบัง 3 โทเค็น คุณอาจต้องแทนที่/usr/bin/env ruby -wด้วยเส้นทางจริงไปยังทับทิมของคุณ (เช่น/usr/local/bin/ruby -w) หรือเรียกใช้จากสคริปต์กระดาษห่อหุ้มหรืออะไรบางอย่าง


2
ขอบคุณ :) ฉันหวังว่าเขาจะไม่รอคำตอบนี้มาสองปีแล้ว
DarkHeart

3
ฉันรอคำตอบนี้มาตลอดสองปีที่ผ่านมา :-) อย่างจริงจังนี่เป็นความคิดที่ชาญฉลาดที่ฉันกำลังมองหา สิ่งที่เตือนนั้นค่อนข้างน่ารำคาญ แต่ฉันคิดหาวิธีบรรเทาได้
cjs

ดีใจที่ฉันสามารถช่วยได้ (ในที่สุด) @CurtSampson ธงของ MRI ถูกฉีกออกจาก Perl โดยที่พวกเขามักจะถูกนำไปใช้โดยไม่จำเป็นในเชลล์หนึ่งสมุทร อย่าลังเลที่จะยอมรับหากคำตอบยังมีประโยชน์สำหรับคุณ :)
bjjb

1
คุณไม่สามารถใช้หลายอาร์กิวเมนต์ใน shebang ใน Linux / usr / bin / env: 'ruby -s': ไม่มีไฟล์หรือไดเร็กทอรีดังกล่าว
FelipeC

13

ฉันสร้าง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

ตรวจสอบว่าอินพุตเป็นประเภทเดียวกันกับค่าดีฟอลต์หรือไม่สร้างตัวเข้าถึงแบบสั้นและแบบยาวพิมพ์ข้อความแสดงข้อผิดพลาดที่สื่อความหมายหากระบุอาร์กิวเมนต์ที่ไม่ถูกต้องและอื่น ๆ

ฉันเปรียบเทียบตัวแยกวิเคราะห์ตัวเลือกหลายตัวโดยใช้ตัวแยกวิเคราะห์ตัวเลือกแต่ละตัวสำหรับปัญหาที่ฉันมี คุณสามารถใช้ตัวอย่างเหล่านี้และข้อมูลสรุปของฉันเพื่อตัดสินใจอย่างมีข้อมูล อย่าลังเลที่จะเพิ่มการใช้งานเพิ่มเติมในรายการ :)


ห้องสมุดเองดูเหมือนว่ามันอาจจะยอดเยี่ยม อย่างไรก็ตามการเปรียบเทียบจำนวนบรรทัดกับ Trollop ไม่ใช่เรื่องที่ไม่จำเป็นเนื่องจากคุณต้องพึ่งพาและกำหนดoptparseว่า (give or take) 1937 lines
Telemachus

6
การเปรียบเทียบจำนวนบรรทัดนั้นใช้ได้อย่างแน่นอนเนื่องจากoptparseเป็นไลบรารีเริ่มต้นกล่าวคือมาพร้อมกับการติดตั้งทับทิมทุกครั้ง Trollopเป็นไลบรารีของบุคคลที่สามดังนั้นคุณต้องนำเข้ารหัสทั้งหมดทุกครั้งที่คุณต้องการรวมไว้ในโครงการ µ-optparse ต้องใช้บรรทัด ~ 70 เสมอเนื่องจากoptparseมีอยู่แล้ว
Florian Pilz

8

ฉันเข้าใจโดยสิ้นเชิงว่าเหตุใดคุณจึงต้องการหลีกเลี่ยงไม่เหมาะสม - มันอาจมากเกินไป แต่มีโซลูชันที่ "เบากว่า" อยู่สองสามวิธี (เมื่อเทียบกับ OptParse) ที่มาเป็นไลบรารี แต่ง่ายพอที่จะทำให้การติดตั้งอัญมณีชิ้นเดียวคุ้มค่า

ตัวอย่างเช่นดูตัวอย่าง OptiFlagนี้ เพียงไม่กี่บรรทัดสำหรับการประมวลผล ตัวอย่างที่ถูกตัดทอนเล็กน้อยที่เหมาะกับกรณีของคุณ:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

มีตันของตัวอย่างที่กำหนดเองเกินไป ฉันจำได้ว่าใช้อันอื่นที่ง่ายกว่า แต่ตอนนี้มันหนีฉันไปแล้ว แต่ฉันจะกลับมาและเพิ่มความคิดเห็นที่นี่หากฉันพบ


อย่าลังเลที่จะแก้ไขคำตอบของคุณเพื่อให้เหมาะกับคำถามที่ชัดเจนยิ่งขึ้น
cjs

4

นี่คือสิ่งที่ฉันใช้สำหรับ args ราคาถูกจริงๆ:

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

main

ดังนั้นถ้าคุณเรียกprogramname foo barมันว่า foo แล้วกด bar มีประโยชน์สำหรับสคริปต์ที่ถูกทิ้ง



3

คุณเคยพิจารณาThorโดย wycats หรือไม่? ฉันคิดว่ามันสะอาดกว่า Optparse มาก หากคุณมีสคริปต์ที่เขียนอยู่แล้วอาจต้องใช้วิธีการอื่นในการจัดรูปแบบหรือปรับโครงสร้างให้เป็น thor แต่จะทำให้ตัวเลือกการจัดการนั้นง่ายมาก

นี่คือตัวอย่างตัวอย่างจาก 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 แมปคำสั่งดังต่อไปนี้โดยอัตโนมัติ:

app install myname --force

ที่แปลงเป็น:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. รับมรดกจาก Thor เพื่อเปลี่ยนคลาสให้เป็นตัวทำแผนที่ตัวเลือก
  2. จับคู่ตัวระบุที่ไม่ถูกต้องเพิ่มเติมกับวิธีการเฉพาะ ในกรณีนี้ให้แปลง -L เป็น: list
  3. อธิบายวิธีการด้านล่างทันที พารามิเตอร์แรกคือข้อมูลการใช้งานและพารามิเตอร์ที่สองคือคำอธิบาย
  4. ระบุตัวเลือกเพิ่มเติมใด ๆ สิ่งเหล่านี้จะถูกจัดเรียงจาก - และ - พารามิเตอร์ ในกรณีนี้จะมีการเพิ่มตัวเลือก a --force และ a -f

ฉันชอบสิ่งที่ทำแผนที่คำสั่งเนื่องจากไบนารีเดียวที่มีคำสั่งย่อยมากมายเป็นสิ่งที่ฉันทำบ่อยๆ ถึงกระนั้นแม้ว่าคุณจะหลีกเลี่ยงจาก 'แสง' คุณสามารถหาวิธีที่ง่ายกว่านี้ในการแสดงฟังก์ชันเดียวกันนี้ได้หรือไม่? จะเกิดอะไรขึ้นถ้าคุณไม่ต้องการพิมพ์--helpผลลัพธ์ จะเกิดอะไรขึ้นถ้า "head myprogram.rb" เป็นผลลัพธ์ความช่วยเหลือ
cjs

3

นี่คือตัวแยกวิเคราะห์ตัวเลือกที่รวดเร็วและสกปรกที่ฉันชอบ:

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

ตัวเลือกเป็นนิพจน์ทั่วไปดังนั้น "-h" จะจับคู่กับ "--help" ด้วย

อ่านง่ายจำง่ายไม่มีไลบรารีภายนอกและโค้ดขั้นต่ำ


ใช่มันจะ. หากนั่นเป็นปัญหาคุณสามารถเพิ่ม regex ได้เช่น/-h(\b|elp)
EdwardTeach

2

Trollopค่อนข้างถูก


นั่นก็คือ < trollop.rubyforge.org > ฉันค่อนข้างชอบ แต่ฉันคิดว่าแม้ว่าฉันจะไม่ได้มองหาห้องสมุดจริงๆ
cjs

จริงอยู่มันคือห้องสมุด อย่างไรก็ตามที่ <800 LOC มันค่อนข้างน้อยมากที่นั่น gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
g33kz0r

1
ฉันคิดว่าบางที 30-50 บรรทัดก็น่าจะดีถ้าฉันจะใช้ "ห้องสมุด" แต่อีกครั้งฉันเดาว่าเมื่อคุณมีไฟล์แยกต่างหากที่เต็มไปด้วยโค้ดการออกแบบ API มีความสำคัญมากกว่าจำนวนบรรทัด ยังฉันไม่แน่ใจว่าฉันต้องการรวมไว้ในสคริปต์แบบครั้งเดียวที่ฉันแค่ต้องการเข้าสู่ไดเร็กทอรี bin บนระบบสุ่ม
cjs

1
คุณได้ย้อนกลับไปแล้วประเด็นคือหลีกเลี่ยงไม่ให้มีกลยุทธ์การปรับใช้ที่ซับซ้อนมากขึ้น
cjs

1
คุณคิดผิดมากเพราะคุณกำลังตีความ (หรือเพิกเฉย) กับความต้องการและความตั้งใจของคนที่ถามคำถามนั้นผิดโดยสิ้นเชิง ฉันขอแนะนำให้คุณอ่านคำถามอีกครั้งโดยละเอียดโดยเฉพาะสองประเด็นสุดท้าย
cjs

2

หากคุณต้องการตัวแยกวิเคราะห์บรรทัดคำสั่งอย่างง่ายสำหรับคำสั่งคีย์ / ค่าโดยไม่ต้องใช้อัญมณี:

แต่จะใช้ได้เฉพาะเมื่อคุณมีคู่คีย์ / ค่าเสมอ

# 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

2

นี่คือข้อมูลโค้ดที่ฉันใช้ที่ด้านบนของสคริปต์ส่วนใหญ่ของฉัน:

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 บรรทัดที่ด้านบนของสคริปต์ใด ๆ ของฉันที่แยกวิเคราะห์บรรทัดคำสั่งและยึดตำแหน่ง args และเปลี่ยนเป็นวัตถุ Hash (โดยปกติจะกำหนดให้กับวัตถุที่ฉันตั้งชื่อว่าarghashในตัวอย่างด้านล่าง)

นี่คือตัวอย่างบรรทัดคำสั่งที่คุณอาจต้องการแยกวิเคราะห์ ...

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

ซึ่งจะกลายเป็น Hash แบบนี้.

 { 
   '-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']ซึ่งประเมินว่าเป็นจริงหากมีอยู่และไม่มีหากไม่มีสวิตช์
  • ทดสอบการมีสวิตช์หรือทางเลือกอื่นของสวิตช์โดยใช้การตั้งค่าเช่น

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

  • ระบุการใช้สวิตช์ที่ไม่ถูกต้องโดยใช้การตั้งค่าเช่น

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

  • ระบุค่าเริ่มต้นสำหรับอาร์กิวเมนต์ที่ขาดหายไปโดยใช้ตัวอย่างง่ายๆHash.merge()เช่นตัวอย่างด้านล่างที่เติมค่าสำหรับ -max = หากไม่ได้ตั้งค่าและเพิ่มอาร์กิวเมนต์ตำแหน่งที่ 4 หากไม่มีการส่งผ่าน

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


(ฉันได้แก้ไขสิ่งนี้เพื่อปรับปรุงการจัดรูปแบบโค้ดโดยส่วนใหญ่ใช้การจัดตำแหน่งเพื่อทำให้โครงสร้างบล็อกและการควบคุมชัดเจนยิ่งขึ้นซึ่งฉันรู้สึกว่ามีความสำคัญอย่างยิ่งในบางสิ่งที่หนาแน่นด้วยเครื่องหมายวรรคตอนนี้ แต่ถ้าคุณไม่ชอบการจัดรูปแบบใหม่โปรดอย่าลังเล เพื่อยกเลิกการแก้ไข)
cjs

นี่เป็นสิ่งที่ดีมากถ้าไม่ใช่สิ่งที่ง่ายที่สุดในโลกในการอ่าน ฉันชอบที่มันแสดงให้เห็นว่าการเปลี่ยนอาร์กิวเมนต์ "ไวยากรณ์" (ที่นี่การอนุญาตตัวเลือกหลังอาร์กิวเมนต์ตำแหน่งและการไม่อนุญาตอาร์กิวเมนต์ตัวเลือกยกเว้นการใช้=) สามารถสร้างความแตกต่างในโค้ดที่คุณต้องการได้
cjs

ขอบคุณสำหรับการจัดรูปแบบใหม่ มันคลุมเครือในการอ่านและสามารถแลกเปลี่ยนความยาวของรหัสเพื่อความชัดเจนได้อย่างง่ายดาย ตอนนี้ฉันเชื่อรหัสนี้ไม่มากก็น้อยฉันก็ปฏิบัติกับมันเหมือนอัญมณีและฉันไม่เคยพยายามที่จะคิดว่ามันทำอะไรภายใต้ผ้าคลุม (ดังนั้นความชัดเจนจึงไม่สำคัญอีกต่อไปในตอนนี้ที่ฉันไว้วางใจ)
David Foster

1

นี้จะคล้ายกับคำตอบที่ได้รับการยอมรับ แต่ใช้ARGV.delete_ifซึ่งเป็นสิ่งที่ผมใช้ในของฉันparser ง่าย ข้อแตกต่างที่แท้จริงเพียงอย่างเดียวคือตัวเลือกที่มีอาร์กิวเมนต์ต้องอยู่ด้วยกัน (เช่น-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

เห็นได้ชัดว่า @WilliamMorgan และฉันก็คิดเหมือนกัน ฉันเพิ่งเปิดตัวเมื่อคืนนี้ไปยัง Github สิ่งที่ฉันเห็นตอนนี้คือไลบรารีที่คล้ายกับ Trollop (ตั้งชื่ออย่างไร) หลังจากทำการค้นหา OptionParser บน Github แล้วให้ดูที่สวิตช์

มีข้อแตกต่างเล็กน้อย แต่ปรัชญาเหมือนกัน ความแตกต่างที่ชัดเจนอย่างหนึ่งคือ Switches ขึ้นอยู่กับ OptionParser


0

ฉันกำลังพัฒนาอัญมณีตัวเลือก parser ของตัวเองที่เรียกว่าเสียงไชโยโห่ร้อง

ฉันเขียนมันเพราะฉันต้องการสร้างอินเทอร์เฟซบรรทัดคำสั่งสไตล์ git และสามารถแยกการทำงานของแต่ละคำสั่งออกเป็นคลาสแยกกันได้อย่างหมดจด แต่ก็สามารถใช้ได้โดยไม่ต้องใช้กรอบคำสั่งทั้งหมดเช่นกัน:

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

ยังไม่มีรุ่นที่เสถียรในขณะนี้ แต่ฉันได้ใช้คุณสมบัติบางอย่างเช่น:

  • ตัวแยกวิเคราะห์ตัวเลือกที่กำหนดเอง
  • การแยกวิเคราะห์อาร์กิวเมนต์ของตัวเลือกที่ยืดหยุ่นซึ่งอนุญาตให้มีทั้งขั้นต่ำและทางเลือก
  • รองรับรูปแบบตัวเลือกมากมาย
  • แทนที่ผนวกหรือเพิ่มในหลาย ๆ อินสแตนซ์ของตัวเลือกเดียวกัน
  • ตัวจัดการตัวเลือกที่กำหนดเอง
  • ตัวจัดการประเภทที่กำหนดเอง
  • ตัวจัดการที่กำหนดไว้ล่วงหน้าสำหรับคลาสไลบรารีมาตรฐานทั่วไป

มีการเน้นคำสั่งมากดังนั้นมันอาจจะหนักไปหน่อยสำหรับการแยกวิเคราะห์บรรทัดคำสั่งง่ายๆ แต่มันทำงานได้ดีและฉันใช้มันในทุกโปรเจ็กต์ของฉัน หากคุณสนใจด้านอินเทอร์เฟซคำสั่งโปรดดูหน้า GitHub ของโครงการเพื่อดูข้อมูลและตัวอย่างเพิ่มเติม


1
ขอแนะนำเสียงโห่ร้อง ใช้งานง่ายและมีตัวเลือกทั้งหมดที่คุณต้องการ
bowsersenior

0

สมมติว่าคำสั่งมีการดำเนินการมากที่สุดหนึ่งรายการและตัวเลือกตามจำนวนที่กำหนดดังนี้:

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

0

https://github.com/soveran/clap

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

46LOC (ที่ 1.0.0) ไม่มีการพึ่งพาตัวแยกวิเคราะห์ตัวเลือกภายนอก ทำให้งานเสร็จ อาจจะไม่โดดเด่นเท่าที่อื่น แต่เป็น 46LOC

หากคุณตรวจสอบรหัสคุณสามารถทำซ้ำเทคนิคพื้นฐานได้อย่างง่ายดาย - กำหนด lambdas และใช้ arity เพื่อให้แน่ใจว่าจำนวน args ที่เหมาะสมตามแฟล็กถ้าคุณไม่ต้องการไลบรารีภายนอกจริงๆ

ง่าย ถูก


แก้ไข : แนวคิดพื้นฐานเกิดขึ้นเนื่องจากฉันคิดว่าคุณอาจคัดลอก / วางลงในสคริปต์เพื่อสร้างตัวแยกวิเคราะห์บรรทัดคำสั่งที่เหมาะสม มันแน่นอนไม่ได้สิ่งที่ฉันจะมอบให้หน่วยความจำ แต่ใช้ 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 ท้ายคำถาม หากคุณไม่สามารถพิมพ์รหัสที่จำเป็นทั้งหมดในคำตอบของคุณได้ที่นี่ก็ไม่ใช่คำตอบสำหรับคำถาม
cjs

จุดดี! ฉันคิดว่าในเวลานั้นฉันคิดว่า lib มีขนาดเล็กพอที่คุณสามารถคัดลอก / วางสิ่งทั้งหมดลงในสคริปต์ใด ๆ ที่คุณกำลังทำงานโดยไม่ต้องพึ่งพาภายนอก แต่ก็ไม่ใช่ซับเดียวที่สะอาดฉันจะมอบให้กับหน่วยความจำ เพื่อตอบสนองจุดที่ 2 ของคุณ ฉันคิดว่าแนวคิดพื้นฐานนั้นแปลกใหม่และเจ๋งพอที่ฉันจะดำเนินการต่อไปและสร้างเวอร์ชันต้มตุ๋นที่ตอบคำถามของคุณได้อย่างเหมาะสมมากขึ้น
Ben Alavi

-1

ฉันจะแบ่งปันตัวแยกวิเคราะห์ตัวเลือกง่ายๆของตัวเองที่ฉันได้ทำมาระยะหนึ่งแล้ว เป็นโค้ด 74 บรรทัดและทำพื้นฐานเกี่ยวกับสิ่งที่ตัวแยกวิเคราะห์ตัวเลือกภายในของ Git ทำ ฉันใช้ OptionParser เป็นแรงบันดาลใจและ Git's

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

ดูเหมือนว่า:

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

คุณไม่ได้ตรวจสอบรหัส ฉันได้ให้คำตอบอื่นโดยใช้รหัสแยกวิเคราะห์
FelipeC

ฉันไม่จำเป็นต้องบอกว่าคุณบอกว่ามันยาว 74 บรรทัด อย่างไรก็ตามฉันเพิ่งดูตอนนี้และยังคงละเมิดประโยคแรกของข้อกำหนด 2 (คำตอบนี้ยังละเมิดหลักการ Stack Overflow ที่คุณควรใส่รหัสในคำตอบของคุณแทนที่จะให้ลิงก์นอกไซต์)
cjs

-1

EasyOptionsไม่ต้องการรหัสแยกวิเคราะห์ตัวเลือกใด ๆ เลย เพียงแค่เขียนข้อความช่วยเหลือต้องการเสร็จสิ้น

## 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 เป็นไฟล์ Ruby ไฟล์เดียวโดยไม่ต้องใช้งบและไม่มีรหัสแยกวิเคราะห์ที่ต้องจำเลย ดูเหมือนว่าคุณต้องการบางสิ่งที่ฝังได้ซึ่งมีประสิทธิภาพเพียงพอ แต่จำง่าย
Renato Silva
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.