В Ruby, как я могу избежать запятой в параметре аргумента с OptionParser? - PullRequest
1 голос
/ 20 июля 2011

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

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

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

Ответы [ 2 ]

1 голос
/ 20 июля 2011

Если ваш пробег:

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

снаряд мини АРГВ:

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

Оболочка преобразует '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"]

Или:

  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"]
0 голосов
/ 20 июля 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 Я предполагаю, что подобна Борну оболочка (некоторые 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-символ.

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

...