Действительно дешевый синтаксический анализ параметров командной строки в Ruby - PullRequest
114 голосов
/ 22 мая 2009

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

Иногда я хочу очень дешево взломать некоторые параметры командной строки в простой скрипт. Забавный способ сделать это, не имея дело с 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-строчный] файл», это то, что я смотрю не только для чистого синтаксиса, но для техники, которая имеет следующие характеристики:

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

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

Ответы [ 20 ]

233 голосов
/ 18 июня 2009

Как автор 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 за опцию людей. Это имеет значение.

76 голосов
/ 23 мая 2009

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

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

#!/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}")
34 голосов
/ 21 ноября 2013

Поскольку никто не упоминал об этом, а название означает относится к дешевому разбору командной строки, почему бы просто не позволить интерпретатору 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 в комментариях ), что ваша оболочка может не поддерживать 3-жетный шебанг; вам может понадобиться заменить /usr/bin/env ruby -w на фактический путь к вашему рубину (например, /usr/local/bin/ruby -w), или запустить его из скрипта-обёртки, или что-то в этом роде.

13 голосов
/ 19 марта 2011

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

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

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

8 голосов
/ 22 мая 2009

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

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

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

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

4 голосов
/ 11 июля 2012

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

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

main

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

3 голосов
/ 23 мая 2009

Рассматривали ли вы Тор от wycats? Я думаю, что это намного чище, чем 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

Тор автоматически отображает команды следующим образом:

app install myname --force

Это преобразуется в:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Унаследовать от Thor, чтобы превратить класс в преобразователь опций
  2. Сопоставить дополнительные недействительные идентификаторы с конкретными методами. В этом случае преобразуйте -L в: список
  3. Опишите метод непосредственно ниже. Первый параметр - это информация об использовании, а второй параметр - описание.
  4. Предоставьте любые дополнительные опции. Это будет маршалинг из - и - params. В этом случае добавляются опции --force и -f.
3 голосов
/ 22 мая 2009

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

if( ARGV.include( '-f' ) )
  file = ARGV[ARGV.indexof( '-f' ) + 1 )]
  ARGV.delete('-f')
  ARGV.delete(file)
end
3 голосов
/ 19 февраля 2014

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...