programing tip

Ruby에서 매우 저렴한 명령 줄 옵션 구문 분석

itbloger 2020. 7. 22. 08:18
반응형

Ruby에서 매우 저렴한 명령 줄 옵션 구문 분석


편집 : 제발, 제발 , 제발 회신하기 전에이 게시물의 하단에 나와있는 두 가지 요구 사항을 참조하십시오. 사람들은 새로운 보석과 라이브러리를 계속 게시하고 요구 사항을 충족시키지 못하는 것을 게시합니다.

때로는 일부 명령 줄 옵션을 간단한 스크립트로 매우 저렴하게 해킹하고 싶습니다. getopts 또는 parsing 등을 다루지 않고 재미있는 방법은 다음과 같습니다.

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

" myprog -i foo bar -q" 에서와 같이 옵션이 아닌 옵션 명령 행 매개 변수를 허용하기 때문에 일반적인 Unix 옵션 구문은 아닙니다 . (Subversion 개발자와 같은 일부 사람들은 이것을 선호합니다. 때로는 저도 그렇게합니다.)

존재하거나없는 옵션은 위의 것보다 훨씬 더 간단하게 구현할 수 없습니다. (한 번의 할당, 하나의 함수 호출, 하나의 부작용) " -f filename " 과 같이 매개 변수를 취하는 옵션을 처리하는 방법도 간단 합니까?

편집하다:

Trollop의 저자가 라이브러리가 "하나의 [800-line] 파일"에 적합하다고 언급 할 때까지 나에게 명확 해지지 않았기 때문에, 이전에하지 않았던 한 가지 점은, 내가 깨끗한 것만을 찾는 것이 아니라는 것입니다 구문, 그러나 다음과 같은 특성을 갖는 기술

  1. 코드 전체를 스크립트 파일에 포함시킬 수 있습니다 (실제 스크립트 자체를 압도하지 않고 수십 줄에 불과할 수 있음) bin. 표준 Ruby 1.8을 사용하는 모든 시스템 디렉토리에 단일 파일을 놓을 수 있습니다. . [5-7] 설치하여 사용하십시오. require 문이없고 몇 가지 옵션을 구문 분석하는 코드가 12 줄 정도 인 Ruby 스크립트를 작성할 수 없으면이 요구 사항이 실패합니다.

  2. 코드는 작고 단순하므로 다른 곳에서 잘라내어 붙여 넣기보다는 트릭을 수행하는 코드를 직접 입력 할 수있을 정도로 충분히 기억할 수 있습니다. 인터넷에 액세스 할 수없는 방화벽 서버 콘솔에 있고 클라이언트가 사용할 수있는 빠른 스크립트를 함께 제공하려는 상황을 생각해보십시오. 나는 당신에 대해 모르지만 (위의 요구 사항을 충족시키지 않는 것 외에도) 45 줄의 단순화 된 마이크로 옵 파스조차 암기하는 것은 내가 신경 쓰는 일이 아닙니다.


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. 그것은 중요.


나는 대한 혐오를 공유 require 'getopts'주로 인의 awesomeness에 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}

내가 일반적으로 사용하는 표준 기술은 다음과 같습니다.

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

아무도 그것을 언급 하지 않았고 제목 저렴한 명령 줄 구문 분석을 의미하기 때문에 루비 인터프리터가 당신을 위해 일하게하지 않겠습니까? -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가 지적한 바와 같이 ) 쉘이 3- 토큰 셰방을 지원하지 않을 수 있다는 것입니다. /usr/bin/env ruby -w루비의 실제 경로 (예 :) 로 바꾸 /usr/local/bin/ruby -w거나 래퍼 스크립트 등에서 실행해야 할 수도 있습니다.


나는 짧지 만 사용하기 쉬운 옵션 파서에 대한이 명백한 요구를 채우기 위해 마이크로파스만들었습니다 . 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

입력이 기본값과 동일한 유형인지 확인하고, 짧고 긴 접근자를 생성하며, 잘못된 인수가 제공되면 설명 오류 메시지를 인쇄합니다.

내가 가진 문제에 대해 각 옵션 파서 를 사용하여 여러 옵션 파서비교했습니다 . 이 예와 요약을 사용하여 유익한 결정을 내릴 수 있습니다. 목록에 더 많은 구현을 추가하십시오. :)


나는 왜 당신이 안검을 피하고 싶어하는지 완전히 이해합니다-너무 많이 얻을 수 있습니다. 그러나 라이브러리로 제공되지만 단일 보석 설치를 가치있게 만들 정도로 간단한 몇 가지 "가벼운"솔루션 (OptParse와 비교)이 있습니다.

예를 들어이 OptiFlag 예제를 확인하십시오 . 처리를 위해 몇 줄만 있습니다. 귀하의 경우에 맞게 약간 잘린 예 :

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

있습니다 사용자 정의 예제의 톤도 . 나는 더 쉬운 다른 것을 사용하는 것을 기억하지만, 지금은 나를 탈출했지만 다시 찾아 와서 여기에 의견을 추가 할 것입니다.


이것이 내가 정말로 저렴한 인수에 사용하는 것입니다.

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

main

따라서 실행 programname foo bar하면 foo를 호출 한 다음 bar를 호출합니다. 버리기 스크립트에 편리합니다.


다음과 같은 것을 시도해 볼 수 있습니다.

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

wycats의 Thor 를 고려 했습니까 ? 나는 그것이 optparse보다 훨씬 깨끗하다고 ​​생각합니다. 이미 작성된 스크립트가있는 경우 스크립트를 포맷하거나 리팩토링하는 것이 더 많은 작업이 될 수 있지만 처리 옵션을 매우 간단하게 만듭니다.

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. 추가 옵션을 제공하십시오. 이것들은-및-매개 변수에서 마샬링됩니다. 이 경우 --force 및 -f 옵션이 추가됩니다.

내가 좋아하는 빠르고 더러운 옵션 파서는 다음과 같습니다.

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

옵션은 정규식이므로 "-h"도 "--help"와 일치합니다.

읽기 쉽고 기억하기 쉽고 외부 라이브러리가 없으며 최소한의 코드.


Trollop 은 꽤 싸다.


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

다음은 대부분의 스크립트 맨 위에서 사용하는 코드 스 니펫입니다.

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

또한 빠르고 더러운 스크립트에 추가 파일이 필요하지 않습니다. 내 솔루션은 거의 당신이 요구하는 것입니다. 명령 줄을 구문 분석하고 위치 인수와 스위치를 해시 객체 (일반적으로 아래 예제에서 arghash 라고 명명 한 객체에 할당)에 고정시키는 스크립트의 맨 위에 10 줄의 코드 조각을 붙여 넣습니다 .

다음은 구문 분석하려는 명령 줄의 예입니다.

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

이 같은 해시 될 것입니다.

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

그 외에도 두 가지 편리한 방법이 해시에 추가됩니다.

  • argc() 스위치가 아닌 인수의 개수를 반환합니다.
  • switches() 존재하는 스위치의 키를 포함하는 배열을 반환합니다

이것은 다음과 같은 빠르고 더러운 것들을 허용하는 것을 의미합니다 ...

  • ( arghash.argc == 2)에 전달 된 스위치에 관계없이 올바른 수의 위치 인수가 있는지 확인하십시오.
  • 위치 인수 이전에 나타나거나 위치 인수와 함께 산재 된 스위치에 관계없이 상대 위치로 위치 인수에 액세스합니다 (예 : arghash[1]항상 두 번째 비스 위치 인수를 얻음 ).
  • 명령 행에서 "--max = 15"와 같이 값이 할당 된 스위치를 지원합니다.이 스위치 arghash['--max=']는 예제 명령 행에서 '15'의 값을 생성하여 액세스 할 수 있습니다 .
  • 명령 줄에 스위치가 존재하는지 여부를 테스트합니다 (예 : 존재하는 arghash['-s']경우 true,없는 경우 nil).
  • 다음과 같은 설정 작업을 사용하여 스위치 또는 스위치의 대안이 있는지 테스트합니다.

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

  • 다음과 같은 설정 작업을 사용하여 유효하지 않은 스위치 사용을 식별

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

  • 설정 Hash.merge()되지 않은 경우 -max =에 대한 값을 채우고 전달되지 않은 경우 네 번째 위치 인수를 추가하는 아래 예와 같이 간단한 인수를 사용하여 누락 된 인수의 기본값을 지정 하십시오.

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


이것은 허용 된 답변과 매우 유사하지만 사용 ARGV.delete_if하는 것은 간단한 파서 에서 사용 하는 것입니다 . 유일한 차이점은 인수가있는 옵션이 함께 있어야한다는 것입니다 (예 :) -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}"

분명히 @WilliamMorgan과 나는 똑같이 생각합니다. 방금 Github에 방금 발표했습니다 .Github에서 OptionParser를 검색 한 후 Trollop과 비슷한 라이브러리입니다 (이름 지정 방법?). 스위치 참조

There are a few differences, but the philosophy is the same. One obvious difference is that Switches is dependent on OptionParser.


I'm developing my own option parser gem called Acclaim.

I wrote it because I wanted to create git-style command line interfaces and be able to cleanly separate the functionality of each command into separate classes, but it can also be used without the entire command framework as well:

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

No stable release as of yet, but I've already implemented some features like:

  • custom option parser
  • flexible parsing of option's arguments that allows for both minimum and optional
  • support for many option styles
  • replace, append or raise on multiple instances of the same option
  • custom option handlers
  • custom type handlers
  • predefined handlers for the common standard library classes

There's a lot of emphasis on commands so it might be a little heavy for simple command line parsing but it works well and I've been using it on all of my projects. If you're interested in the command interface aspect then check out the project's GitHub page for more information and examples.


Suppose a command has at most one action and arbitrary number of options like this:

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

The parsing without validation may be like this:

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

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

https://github.com/soveran/clap

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

46LOC (at 1.0.0), no dependency on external option parser. Gets the job done. Probably not as full featured as others, but it's 46LOC.

If you check the code you can pretty easily duplicate the underlying technique -- assign lambdas and use the arity to ensure the proper number of args follow the flag if you really don't want an external library.

Simple. Cheap.


EDIT: the underlying concept boiled down as I suppose you might copy/paste it into a script to make a reasonable command line parser. It's definitely not something I would commit to memory, but using the lambda arity as a cheap parser is a novel idea:

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

I'm going to share my own simple option parser that I've been working on for some time. It's merely 74 lines of code, and it does the basics of what the Git's internal option parser does. I took OptionParser as inspiration, and also Git's.

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

It looks like this:

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

EasyOptions does not require any option parsing code at all. Just write the help text, require, done.

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

참고URL : https://stackoverflow.com/questions/897630/really-cheap-command-line-option-parsing-in-ruby

반응형