Действительно дешевый вариант командной строки в Ruby

РЕДАКТИРОВАТЬ: пожалуйста, пожалуйста прочитайте два требования, перечисленные внизу этого сообщения, прежде чем отвечать. Люди продолжают публиковать свои новые драгоценные камни и библиотеки и многое другое, что явно не соответствует требованиям.

Иногда я хочу очень дешево взломать некоторые параметры командной строки в простой script. Увлекательный способ сделать это, не имея дело с 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", но я могу жить с этим. (Некоторые люди, такие как разработчики Subversion, предпочитают это. Иногда я тоже.)

Опция, которая просто присутствует или отсутствует, не может быть реализована гораздо проще, чем предыдущая. (Одно назначение, один вызов функции, один побочный эффект.) Существует ли одинаковый простой способ справиться с параметрами, которые принимают параметр, например "-f filename"?

EDIT:

Один момент, который я не делал раньше, потому что мне не стало ясно, пока автор Trollop не упомянул, что библиотека подходит "в одном [800-строчном] файле", это то, что я ищу не только для чистого синтаксиса, но для метода, который имеет следующие характеристики:

  • Весь код может быть включен в файл script (без подавления самого фактического script, который может быть только несколькими десятками строк), так что можно удалить один файл в bin в любой системе со стандартной версией Ruby 1.8. [5-7] и используйте ее. Если вы не можете написать Ruby script, у которого нет требуемых операторов, и где код для разбора нескольких параметров находится под дюжиной строк или около того, вы не выполняете это требование.

  • Код небольшой и достаточно простой, чтобы его можно было запомнить достаточно, чтобы напрямую вводить код, который будет делать трюк, а не вырезать и вставлять из другого места. Подумайте о ситуации, когда вы находитесь на консоли межсетевого экрана без доступа к Интернету, и хотите скопировать быстрый script для использования клиентом. Я не знаю о вас, но (помимо того, что не удалось выполнить вышеописанное требование), запоминание даже 45 строк упрощенного микрооптикажа не является чем-то, что мне нужно делать.

Ответ 1

Здесь стандартная техника, которую я обычно использую:

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

Ответ 2

Как автор 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 для каждого участника. Это важно.

Ответ 3

Я разделяю ваше отвращение к 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}

Ответ 4

Так как никто не упомянул об этом, а название относится к дешевому разбору командной строки, почему бы просто не позволить интерпретатору ruby выполнить эту работу за вас? Если вы -s переключатель -s (например, в своем -s), вы получите простые простые переключатели, назначенные однобуквенным глобальным переменным. Вот ваш пример использования этого переключателя:

#!/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, если вы попытаетесь переключиться как -: так что там есть некоторая проверка. Конечно, у вас не может быть никаких переключателей после не -s ведьмы, но если вам нужно что-то необычное, вам действительно следует использовать как минимум OptionParser. На самом деле, единственное, что меня раздражает в этой технике, это то, что вы получите предупреждение (если вы их включили) при доступе к неустановленной глобальной переменной, но она все еще ложная, поэтому она отлично работает для одноразовых инструментов и быстрых скрипты.

Одно замечание (как указал FelipeC в комментариях), что ваша оболочка может не поддерживать 3- жетный шебанг; вам может понадобиться заменить /usr/bin/env ruby -w реальным путем к вашему ruby (например, /usr/local/bin/ruby -w), или запустить его из сценария оболочки или чего-то еще.

Ответ 5

Я построил 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!

Вызов script с помощью -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

Он проверяет, имеет ли вход тот же тип, что и значение по умолчанию, генерирует короткие и длинные аксессоры, печатает дескриптивные сообщения об ошибках, если указаны недопустимые аргументы и многое другое.

I сравнил несколько опций-парсеров, используя каждый параметр-парсер для проблемы, которую я имел. Вы можете использовать эти примеры и свое резюме для принятия информационного решения. Не стесняйтесь добавлять в список дополнительные реализации.:)

Ответ 6

Я полностью понимаю, почему вы хотите избежать optparse - это может стать слишком большим. Но есть несколько далеких "легких" решений (по сравнению с OptParse), которые поставляются в виде библиотек, но достаточно просты, чтобы сделать установку одного драгоценного камня достойной.

Например, проверьте этот пример OptiFlag. Всего несколько строк для обработки. Немного усеченный пример с учетом вашего случая:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

Есть тонны настраиваемых примеров. Я вспоминаю использование другого, что было еще проще, но теперь он ускользнул от меня, но я вернусь и добавлю комментарий здесь, если найду его.

Ответ 7

Это то, что я использую для действительно, действительно дешевых аргументов:

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

main

поэтому, если вы запустите programname foo bar, он вызывает foo, а затем bar. Он удобен для раздаточных скриптов.

Ответ 8

Вы можете попробовать что-то вроде:

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

Ответ 9

Рассматривали ли вы Thor wycats? Я думаю, что это намного чище, чем оппонент. Если у вас уже есть script, может потребоваться некоторая работа по форматированию или рефакторинг его для тора, но он делает очень удобными параметры обработки.

Вот пример фрагмента из 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
  • Наследовать от Thor, чтобы превратить класс в опцию mapper
  • Сопоставьте дополнительные недействительные идентификаторы с определенными методами. В этом случае преобразуйте -L в: list
  • Опишите метод, приведенный ниже. Первым параметром является информация об использовании, а второй параметр - описание.
  • Предоставьте любые дополнительные параметры. Они будут маршализированы из-и-params. В этом случае добавляется опция -force и a -f.

Ответ 10

Вот мой любимый быстрый и грязный парсер параметров:

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

Параметры являются регулярными выражениями, поэтому "-h" также будет соответствовать "--help".

Считываемое, легко запоминающееся, внешняя библиотека и минимальный код.

Ответ 11

Trollop довольно дешево.

Ответ 12

Если вам нужен простой синтаксический анализатор командной строки для команд key/value без использования драгоценных камней:

Но этот только работает, если у вас всегда есть пары ключ/значение.

# 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

Ответ 13

Вот фрагмент кода, который я использую в верхней части большинства моих скриптов:

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-строчный фрагмент кода вверху любого из моих сценариев, который анализирует командную строку, вставляет позиционные аргументы и переключается в объект Hash (обычно назначается объекту, который я назвал arghash в приведенных ниже примерах).

Вот пример командной строки, которую вы можете проанализировать...

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

Который стал бы таким хешем.

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

В дополнение к этому, в Hash добавлены два вспомогательных метода:

  • argc() вернет количество не -s аргументов ведьмы.
  • switches() вернет массив, содержащий ключи для имеющихся ключей

Это означает, что можно позволить некоторые быстрые и грязные вещи, как...

  • Подтвердите, что у меня есть правильное количество позиционных аргументов независимо от переданных ключей (arghash.argc == 2)
  • Получите доступ к позиционным аргументам по их относительной позиции, независимо от того, arghash[1] переключатели появляются раньше или чередуются с позиционными аргументами (например, arghash[1] всегда получает второй не -s колдовский аргумент).
  • Поддержите присвоенные значения переключатели в командной строке, такие как "--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 =, если он не был установлен, и добавляет 4-й позиционный аргумент, если он не был передан.

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

Ответ 14

Это очень похоже на принятый ответ, но с использованием 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}"

Ответ 15

По-видимому @WilliamMorgan и я думаю, что так. Я только что выпустил прошлую ночь в Github, что я теперь вижу, это аналогичная библиотека для Trollop (названа так?) После того, как выполнил поиск OptionParser в Github, см. Switches

Есть несколько отличий, но философия такая же. Одно очевидное различие заключается в том, что коммутаторы зависят от OptionParser.

Ответ 16

Я разрабатываю свой собственный синтаксический анализатор параметров, который называется Acclaim.

Я написал это, потому что я хотел создать интерфейсы командной строки git -style и иметь возможность четко разделить функциональные возможности каждой команды на отдельные классы, но ее также можно использовать и без всей оболочки команд:

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

Пока нет стабильной версии, но я уже реализовал некоторые функции, такие как:

  • настраиваемый парсер параметров
  • гибкий анализ аргументов параметров, который позволяет использовать как минимальные, так и необязательные
  • поддержка многих стилей параметров
  • заменить, добавить или поднять на нескольких экземплярах одной и той же опции
  • пользовательские обработчики параметров
  • обработчики пользовательского типа
  • предопределенные обработчики для стандартных классов стандартной библиотеки

Здесь много внимания уделяется командам, поэтому для простого разбора командной строки может быть немного тяжело, но он работает хорошо, и я использую его во всех своих проектах. Если вас интересует аспект командного интерфейса, посмотрите страницу проекта GitHub для получения дополнительной информации и примеров.

Ответ 17

Предположим, что команда имеет не более одного действия и произвольное количество таких параметров:

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

Ответ 18

https://github.com/soveran/clap

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

46LOC (на 1.0.0), нет зависимости от внешнего анализатора опций. Получает работу сделано. Вероятно, не так полнофункциональный, как другие, но это 46LOC.

Если вы проверите код, вы можете довольно легко продублировать базовую технику - назначьте лямбды и используйте арность, чтобы убедиться, что правильное количество аргументов следует за флагом, если вы действительно не хотите использовать внешнюю библиотеку.

Просто. Дешевые.


РЕДАКТИРОВАТЬ: основная концепция сводится к минимуму, как я полагаю, вы могли бы скопировать/вставить его в сценарий, чтобы сделать разумный анализатор командной строки. Это определенно не то, что я бы запомнил, но использование лямбда-арности в качестве дешевого синтаксического анализатора является новой идеей:

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

Ответ 19

Я собираюсь поделиться своим собственным простым парсером параметров, над которым я работал некоторое время. Это всего лишь 74 строки кода, и он основывает то, что делает внутренний анализатор параметров Git. Я использовал OptionParser как вдохновение, а также Git.

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

Ответ 20

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