Действительно дешевый синтаксический анализ параметров командной строки в 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 ]

2 голосов
/ 13 июля 2018

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

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

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

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

2 голосов
/ 28 января 2014

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

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

# 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 голосов
/ 26 мая 2009

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

1 голос
/ 18 января 2018

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

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
0 голосов
/ 19 июля 2014

https://github.com/soveran/clap

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

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

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

Simple. Дешево.


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

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...
0 голосов
/ 01 октября 2013

Я собираюсь поделиться своим собственным простым парсером опций, над которым я работал в течение некоторого времени. Это всего лишь 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
0 голосов
/ 07 июля 2013

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

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 голосов
/ 23 декабря 2009

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

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

0 голосов
/ 12 января 2012

Я разрабатываю мой собственный гем анализатора опций под названием Acclaim .

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

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

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

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

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

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