提问人:HippoMan 提问时间:2/23/2023 最后编辑:HippoMan 更新时间:5/6/2023 访问量:204
在 ruby 中,当文件名包含某些字符时,optparse 会引发错误
In ruby, optparse raises error when filename contains certain characters
问:
我在 Linux 下的 ruby 程序 () 中使用。如果任何命令行参数是包含“特殊”字符的文件名,则该方法将失败,并出现以下错误:optparse
ruby 2.7.1p83
parse!
invalid byte sequence in UTF-8
这是失败的代码......
parser = OptionParser.new {
|opts|
... etc. ...
}
parser.parse! # error occurs here
我知道在 ruby 中进行编码的方法和其他方法。但是,发生错误的地方是在库例程 () 中,我无法控制此库例程如何处理字符串。scrub
OptionParser#parse!
我可以预处理命令行参数,并将这些参数中的特殊字符替换为可接受的编码,但是,如果参数是文件名,我将无法稍后在程序中打开该文件,因为我接受到程序中的文件名将从文件的原始名称更改。
我可以做一些复杂的事情,比如预先遍历参数,构建一个哈希图,其中键是编码的参数,值是原始参数,将 ARGV 值更改为编码值,使用 解析编码的参数,然后在完成后遍历生成的参数,并使用哈希映射在将编码参数替换为其原始值的过程中......然后继续执行该程序。OptionParser
OptionParser
但我希望在红宝石中能有一种更简单的方法来解决这个问题。
提前感谢您的任何想法或建议。
更新:这是更详细的信息...
为了测试这一点,我编写了以下最小程序:rtest.rb
#!/usr/bin/env ruby
# -*- ruby -*-
require 'optparse'
parser = OptionParser.new {
}
parser.parse!
Process.exit(0)
我按如下方式运行它,当前目录中唯一存在的文件是它本身,另一个文件具有此名称:...rtest.rb
Äfoo
export LC_TYPE='en_us.UTF-8'
export LC_COLLATE='en_us.UTF-8'
./rtest.rb *
它生成了以下错误和堆栈跟踪...
Traceback (most recent call last):
7: from /home/hippo/bin/rtest.rb:8:in `<main>'
6: from /opt/rubies/ruby-2.7.1/lib/ruby/2.7.0/optparse.rb:1691:in `parse!'
5: from /opt/rubies/ruby-2.7.1/lib/ruby/2.7.0/optparse.rb:1666:in `permute!'
4: from /opt/rubies/ruby-2.7.1/lib/ruby/2.7.0/optparse.rb:1569:in `order!'
3: from /opt/rubies/ruby-2.7.1/lib/ruby/2.7.0/optparse.rb:1575:in `parse_in_order'
2: from /opt/rubies/ruby-2.7.1/lib/ruby/2.7.0/optparse.rb:1575:in `catch'
1: from /opt/rubies/ruby-2.7.1/lib/ruby/2.7.0/optparse.rb:1579:in `block in parse_in_order'
/opt/rubies/ruby-2.7.1/lib/ruby/2.7.0/optparse.rb:1579:in `===': invalid byte sequence in UTF-8 (ArgumentError)
这是出现在文件的相关部分中的内容。见行.../opt/rubies/ruby-2.7.1/lib/ruby/2.7.0/optparse.rb
1579
1572 def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc:
1573 opt, arg, val, rest = nil
1574 nonopt ||= proc {|a| throw :terminate, a}
1575 argv.unshift(arg) if arg = catch(:terminate) {
1576 while arg = argv.shift
1577 case arg
1578 # long option
1579 when /\A--([^=]*)(?:=(.*))?/m
1580 opt, rest = $1, $2
换句话说,由于此编码问题,参数上的正则表达式匹配失败。
当我有时间时(不幸的是,不是马上),我会在该模块中放入一些代码来对变量进行编码,看看这是否可以解决问题。arg
进一步更新:我在 下运行,提供的 ruby 版本是 2.7.0。我还设法在我的旧盒子上运行 2.7.1。此错误在两种环境中都会发生。在尝试 2.7.7 版或 3.x 版之前,我必须安装较新版本的 ruby 或从源代码编译它。Ubuntu 20.0.4
debian 8
另一个更新:我有一些意想不到的空闲时间,所以我从源代码构建了 ruby-3.3.0 并重新运行测试。我遇到了同样的错误!
% /opt/local/rubies/ruby-3.3.0/bin/ruby ./rtest.rb *
/opt/local/rubies/ruby-3.3.0/lib/ruby/3.3.0+0/optparse.rb:1640:in `===': invalid byte sequence in UTF-8 (ArgumentError)
from /opt/local/rubies/ruby-3.3.0/lib/ruby/3.3.0+0/optparse.rb:1640:in `block in parse_in_order'
from /opt/local/rubies/ruby-3.3.0/lib/ruby/3.3.0+0/optparse.rb:1636:in `catch'
from /opt/local/rubies/ruby-3.3.0/lib/ruby/3.3.0+0/optparse.rb:1636:in `parse_in_order'
from /opt/local/rubies/ruby-3.3.0/lib/ruby/3.3.0+0/optparse.rb:1630:in `order!'
from /opt/local/rubies/ruby-3.3.0/lib/ruby/3.3.0+0/optparse.rb:1739:in `permute!'
from /opt/local/rubies/ruby-3.3.0/lib/ruby/3.3.0+0/optparse.rb:1764:in `parse!'
from ./rtest.rb:8:in `<main>'
但是,我现在认为发生错误是因为文件名以不寻常的方式编码。如果我在该目录中这样做,我会看到这个,这就是我所期望的:echo *
% echo *
Äfoo rtest.rb
但是,如果我在同一目录中执行,我会看到以下内容:/bin/ls
% /bin/ls *
''$'\304''foo' rtest.rb
甚至操作系统也无法识别名称如下的文件......
% /bin/cat 'Äfoo'
/bin/cat: Äfoo: No such file or directory
但是,如果我使用较长的编码文件名,操作系统访问该文件没有问题......
% /bin/cat ''$'\304''foo
File contents
File contents
该命令似乎知道如何将文件名编码为 ,但 ruby 似乎不知道如何做到这一点。ls
Äfoo
''$'\304''foo
答:
注意:我更喜欢我的另一个答案。但是,我也保留了这个答案,以防有人仍然感兴趣。
根据我最初问题下面的讨论,尤其是根据@Schwern的评论,似乎此错误是由于我一直遇到的文件名中一组不可解析和不可编码的字节。因此,在 ruby 中可能无法正确处理这样命名的文件。
需要明确的是,任何包含此类不可编码字节的字符串都会出现此问题,而不仅仅是文件名。
因此,我只是在命令行上检查这种不可解析的字符串,如果遇到任何错误,我会退出 ruby 脚本并出现错误。
以下是我改进的测试程序,它显示了我现在如何处理这种情况:
#!/usr/bin/env ruby
# -*- ruby -*-
require 'optparse'
badargs = []
ARGV.each {
|arg|
begin
# The following test is being used because this
# error shows up in a regex match of the argument
# when done within the OptionParser code. There are
# no doubt other and possibly better ways to trigger
# this error, but this is good enough for me for the
# time being, especially because this is just an
# illustrative test program.
arg =~ /./m
rescue
badargs << arg
end
}
nbad = badargs.length
if nbad > 0 then
if nbad == 1 then
object = 'this command-line argument'
else
object = 'these command-line arguments'
end
puts "Unable to parse #{object}: #{badargs}"
Process.exit(1)
end
parser = OptionParser.new {
}
parser.parse!
Process.exit(0)
我暂时将其视为我的“答案”,除非出现更好的情况。
现在确实出现了一个更好的答案。在这里查看我的另一个答案。
我想出了这个略显诡异的解决方法,在我看来,它比我在这里发布的另一个答案更好。
因此,这是我的首选答案。
我编写了一个名为预处理器的预处理器,它试图根据程序的命令行参数确定哪种编码可能适用于给定的 ruby 程序(请参阅下面的代码以检查代码)。然后,所有符合的 ruby 程序只需要按如下方式编写......ruby-preproc
ruby-preproc
#!/usr/bin/env ruby-preproc
# -*- ruby -*-
[ ... normal ruby code goes here ... ]
如果调用使用此约定的 ruby 程序,那么它将被正常调用:the-script.rb
./the-script.rb args ...
此外,此预处理器还支持使用特殊的、可选的初始参数 .在这种情况下,将强制执行指定的编码,而不是检查参数列表。例如,对于任何设置为使用此预处理器的 ruby 程序,可以执行以下操作......-E<encoding>
./the-script.rb -EISO-8859-1 args
如果未给出初始参数,则处理器会检查所有已指定的命令行参数,并查找适用于每个参数的编码。如果找到此类编码,则使用指定的编码运行脚本。-E<encoding>
ruby-preproc
这是代码(这是我昨天在这个“答案”中发布的原版的改进版本)......ruby-preproc
#!/opt/local/rubies/ruby-3.3.0/bin/ruby
# -*- ruby -*-
# Note that any recent standard ruby executable can be
# used in the initial shebang line.
#
# Also note that the following construct is a way to
# obtain the value of whatever appears in the shebang
# line, so that this file name doesn't need to be
# entered twice in this program:
require 'rbconfig'
ruby_executable = File.join(RbConfig::CONFIG["bindir"],
RbConfig::CONFIG["RUBY_INSTALL_NAME"] +
RbConfig::CONFIG["EXEEXT"])
$default_enc = 'default'
$tried = []
$success = true
$prog = ARGV[0]
nargs = ARGV.length
if nargs > 1 && ARGV[1] =~ /^-E(.+)$/ then
# If we're here, then the -E<encoding> parameter
# appears on the command line. Just use that
# specified encoding.
$curr_enc = $1
$success = true
$args = [ $prog ] + ARGV[2..-1]
else
# If we're here, no -E<encoding> was specified,
# so examine the command-line arguments in order
# to find out whether any of the following listed
# encodings might work properly for all of these
# arguments.
#
# Put as many encodings into this list as is desired.
# And I believe it's best to also include $default_enc.
encodings_to_try = [
$default_enc,
'UTF-8',
'ISO-8859-1',
]
$curr_enc = $default_enc
$args = ARGV
encodings_to_try.each {
|enc|
$success = true
$curr_enc = enc
$args.each {
|arg|
newarg = arg.dup
begin
if enc != $default_enc then
newarg.encode!(enc)
end
newarg =~ /.?/m
rescue Exception => e
if e.to_s.include?('invalid byte sequence')
$success = false
break
end
end
}
if $success then
break
else
$tried << $curr_enc
end
}
end
if $success then
if $curr_enc == $default_enc then
Process.exec(ruby_executable, *$args)
else
Process.exec(ruby_executable, "-E#{$curr_enc}", *$args)
end
else
tlen = $tried.length
if tlen < 1 then
via = ''
elsif tlen == 1 then
via = " via this encoding: #{$tried[0]}"
else
via = " via any of these encodings: #{$tried}"
end
puts("Unable to run `#{$prog}` because one or more arguments cannot be parsed#{via}")
Process.exit(1)
end
下一个:Ruby 中的解析表
评论
./test.rb -r testěščřžýáíéúů
en_US.UTF-8
OptionParser
CSV
OptionParser
LC_TYPE
LC_COLLATE
en_us.UTF-8
Äfoo
Ä