В Ruby, как я могу избежать запятой в параметре аргумента с помощью OptionParser?

Учитывая следующий код:

options = {}
optparse = OptionParser.new do |opts|
    opts.on('-t', '--thing [THING1,THING2]', Array, 'Set THING1, THING2') do |t|
      options[:things] = t
    end
end

Если в THING1 есть запятая, как я могу предотвратить разделение OptionParser на нее?

Пример дела: ./scrit.rb -t 'foo,bar',baz. В этом случае я хочу, чтобы options[:things] было ['foo,bar', 'baz']

Это вообще возможно?


person Scott Smith    schedule 19.07.2011    source источник


Ответы (2)


Если ваш бег:

./scrit.rb -t 'foo,bar',baz

проход оболочки АРГВ:

["-t", "foo,bar,baz"]

Shell преобразует 'foo,bar',baz в foo,bar,baz:

$ strace -e trace=execve ./scrit.rb -t 'foo,bar',baz
execve("./scrit.rb", ["./scrit.rb", "-t", "foo,bar,baz"], [/* 52 vars */]) = 0
execve("/home/scuawn/bin/ruby", ["ruby", "./scrit.rb", "-t", "foo,bar,baz"], [/* 52 vars */]) = 0

Вы можете использовать другой разделитель:

  opts.on('-t', '--thing [THING1,THING2]', Array, 'Set THING1, THING2') do |t|
    options[:things] = t
    options[:things][0] = options[:things][0].split(":")
  end

$ ./scrit.rb -t foo:bar,baz
[["foo", "bar"], "baz"]

Or:

  opts.on('-t', '--thing [THING1,THING2]', Array, 'Set THING1, THING2') do |t|
    options[:things] = t
    options[:things] = options[:things].length == 3 ? [[options[:things][0],options[:things][1]],options[:things][2]] : options[:things]
  end

$ ./scrit.rb -t foo,bar,baz
[["foo", "bar"], "baz"]
person scuawn    schedule 20.07.2011
comment
К сожалению, я не могу изменить наличие запятой в THING1. :( - person Scott Smith; 20.07.2011
comment
Затем обработайте options[:things]: options[:things] = options[:things].length == 3 ? [[опции[:вещи][0],опции[:вещи][1]],опции[:вещи][2]] : опции[:вещи] - person scuawn; 20.07.2011

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

./scrit.rb -t 'foo,bar',baz
./scrit.rb -t foo,'bar,baz'
./scrit.rb -t 'foo,bar,baz'
./scrit.rb -t foo,bar,baz
./scrit.rb -t fo"o,b"ar,baz
./scrit.rb -t foo,b\ar,baz
# obviously many more variations are possible

Вы можете проверить это так:

ruby -e 'f=ARGV[0];ARGV.each_with_index{|a,i|puts "%u: %s <%s>\n" % [i,a==f,a]}'\
 'foo,bar',baz foo,'bar,baz' 'foo,bar,baz' foo,bar,baz fo"o,b"ar,baz foo,b\ar,baz

1 Я предполагаю, что оболочка похожа на Bourne (некоторые варианты sh, такие как zsh, bash, ksh, тире и т. д.).


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

split_on_semicolons = Object.new
OptionParser.accept split_on_semicolons do |s,|
  s.split ';'
end
⋮
opts.on('-t', '--thing [THING1;THING2]', split_on_semicolons, 'Set THING1, THING2 (semicolon must be quoted to protect it from the shell)') do |t|
  options[:things] = t
end

Оболочка придает особое значение точке с запятой, поэтому она должна быть экранирована или заключена в кавычки (иначе она служит безусловным разделителем команд (например, echo foo; sleep 2; echo bar)):

./scrit.rb -t foo,bar\;baz
./scrit.rb -t foo,bar';'baz
./scrit.rb -t 'foo,bar;baz'
# et cetera

«Синтаксический анализ», выполняемый при указании Array, почти в точности аналогичен базовому str.split(',') (он также отбрасывает пустые строковые значения), поэтому нет возможности напрямую указать escape-символ.

Если вы хотите придерживаться запятых, но ввести «экранирующий символ», то вы можете немного обработать значения в своем блоке OptionParser#on, чтобы сшить определенные значения обратно вместе:

# use backslash as an after-the-fact escape character
# in a sequence of string values,
#   if a value ends with a odd number of backslashes, then
#     the last backslash should be replaced with
#     a command concatenated with the next value
#   a backslash before any other single character is removed
# 
# basic unsplit: (note doubled backslashes due to writing these as Ruby values)
#     %w[foo\\ bar baz] => %w[foo,bar baz]
#
# escaped, trailing backslash is not an unsplit:
#     %w[foo\\\\ bar baz] => %w[foo\\ bar baz]
#
# escaping [other, backslash, split], also consecutive unsplits
#     %w[f\\o\\\\o\\ \\\\\\bar\\\\\\ baz] => %w[fo\\o,\\bar\\,baz]

def unsplit_and_unescape(orig_values)
  values = []
  incompleteValue = nil
  orig_values.each do |val|
    incomplete = /\\*$/.match(val)[0].length.odd?
    val.gsub! /\\(.)/, '\1'
    val = incompleteValue + ',' + val if incompleteValue
    if incomplete
      incompleteValue = val[0..-2]
    else
      values << val
      incompleteValue = nil
    end
  end
  if incompleteValue
    raise ArgumentError, 'Incomplete final value'
  end
  values
end
⋮
opts.on('-t', '--thing [THING1,THING2]', Array, 'Set THING1, THING2 (use \\, to include a comma)') do |t|
  options[:things] = unsplit_and_unescape(t)
end

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

./scrit.rb -t foo\\,bar,baz
./scrit.rb -t 'foo\,bar,baz'
./scrit.rb -t foo'\,'bar,baz
./scrit.rb -t "foo\\,bar,baz"
./scrit.rb -t fo"o\\,ba"r,baz
# et cetera

2 В отличие от Ruby, одинарная кавычка оболочки полностью буквальна (например, обратная косая черта не интерпретируется), поэтому часто это хороший выбор, когда вам нужно встроить любую другую оболочку. специальные символы (такие как обратная косая черта и двойные кавычки).

person Chris Johnsen    schedule 20.07.2011