给出以下代码:
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']
这甚至可能吗?
答案 0 :(得分:1)
如果你跑:
./scrit.rb -t 'foo,bar',baz
shell传递ARGV:
["-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"]
或者:
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"]
答案 1 :(得分:0)
首先,shell 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的shell(一些 sh - 变体,如 zsh , bash , ksh , dash 等等。
如果要切换到其他分隔符,可以这样做:
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
shell赋予分号特殊含义,因此必须对其进行转义或引用(否则它将作为无条件命令分隔符(例如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(',')
(它也会删除空字符串值),因此无法直接指定转义字符。
如果您想坚持使用逗号但引入“转义字符”,那么您可以在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
然后你可以像这样在shell中运行它(反斜杠对于shell也是特殊的,所以它必须被转义或引用 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不同,shell的单引号完全是文字的(例如没有解释反斜杠),因此当你需要嵌入任何其他 shell特殊字符(如反斜杠和双引号)。