如何使用Ruby';s readlines.grep是否用于UTF-16文件?

如何使用Ruby';s readlines.grep是否用于UTF-16文件?,ruby,command-line,utf-16le,Ruby,Command Line,Utf 16le,给定由以下命令创建的以下两个文件: $ printf "foo\nbar\nbaz\n" | iconv -t UTF-8 > utf-8.txt $ printf "foo\nbar\nbaz\n" | iconv -t UTF-16 > utf-16.txt $ file utf-8.txt utf-16.txt utf-8.txt: ASCII text utf-16.txt: Little-endian UTF-16 Unicode text 我希望在UTF-16格式的文

给定由以下命令创建的以下两个文件:

$ printf "foo\nbar\nbaz\n" | iconv -t UTF-8 > utf-8.txt
$ printf "foo\nbar\nbaz\n" | iconv -t UTF-16 > utf-16.txt
$ file utf-8.txt utf-16.txt
utf-8.txt:  ASCII text
utf-16.txt: Little-endian UTF-16 Unicode text
我希望在UTF-16格式的文件中找到匹配模式,方法与使用Ruby的UTF-8中相同

以下是UTF-8文件的工作示例:

$ ruby -e 'puts File.open("utf-8.txt").readlines.grep(/foo/)'
foo
但是,它不适用于UTF-16LE格式的文件:

$ ruby -e 'puts File.open("utf-16.txt").readlines.grep(/foo/)'
Traceback (most recent call last):
    3: from -e:1:in `<main>'
    2: from -e:1:in `grep'
    1: from -e:1:in `each'
-e:1:in `===': invalid byte sequence in US-ASCII (ArgumentError)
但是它在
foo
之前打印了一些无效字符(
ÿþ
),其次我不知道转换后如何使用
grep
方法(它报告为未定义的方法)

如何为UTF-16文件使用
readlines.grep()
方法?
或其他简单方法,其中我的目标是使用特定的正则表达式模式打印行


理想情况下,在一行中,因此该命令可用于CI测试

以下是一些真实场景:

ruby -e 'if File.readlines("utf-16.log").grep(/[1-9] error/) {exit 1}; end'

但是由于日志文件的UTF-16格式,该命令无法工作。

简短回答:

您几乎拥有了它,只需说出要替换的字符(我猜是无效字符和未定义字符):

另外,我认为您不需要强制编码

如果要忽略打开时的
BOM
转换并使用
readlines
,可以使用:

 ruby -e 'puts File.open("utf-16.txt", mode: "rb:BOM|UTF-16LE:UTF-8").readlines.grep(/foo/)'
更多详细信息:

执行此操作时获得无效字符的原因:

$ruby -e 'puts File.open("utf-16.txt", "r").read.force_encoding("ISO-8859-1").encode("utf-8", replace: nil)'
ÿþfoo
bar
baz
在每个Unicode文件的开头可以有字节顺序标记,显示字节顺序和编码形式。在您的例子中,它是
FE FF
(表示小尾端UTF-16),是无效的UTF-8字符

您可以通过调用
encode
而不使用
force\u编码来验证:

$ruby -e 'puts File.open("utf-16.txt", "r").read.encode("utf-8")'
��foo
bar
baz
黑框中的问号用于替换未知、无法识别或不可表示的字符


您可以查看BOM的更多信息。

简短回答:

您几乎拥有了它,只需说出要替换的字符(我猜是无效字符和未定义字符):

另外,我认为您不需要强制编码

如果要忽略打开时的
BOM
转换并使用
readlines
,可以使用:

 ruby -e 'puts File.open("utf-16.txt", mode: "rb:BOM|UTF-16LE:UTF-8").readlines.grep(/foo/)'
更多详细信息:

执行此操作时获得无效字符的原因:

$ruby -e 'puts File.open("utf-16.txt", "r").read.force_encoding("ISO-8859-1").encode("utf-8", replace: nil)'
ÿþfoo
bar
baz
在每个Unicode文件的开头可以有字节顺序标记,显示字节顺序和编码形式。在您的例子中,它是
FE FF
(表示小尾端UTF-16),是无效的UTF-8字符

您可以通过调用
encode
而不使用
force\u编码来验证:

$ruby -e 'puts File.open("utf-16.txt", "r").read.encode("utf-8")'
��foo
bar
baz
黑框中的问号用于替换未知、无法识别或不可表示的字符


您可以在BOM上查看更多信息。

虽然Viktor的回答在技术上是正确的,但将整个文件从
UTF-16LE
重新编码到
UTF-8
是不必要的,可能会影响性能。实际上,您只需要以相同的编码构建regexp:

puts File.open(
  "utf-16.txt", mode: "rb:BOM|UTF-16LE"
).readlines.grep(
  Regexp.new "foo".encode(Encoding::UTF_16LE)
)
#⇒ foo

虽然Viktor的回答在技术上是正确的,但将整个文件从
UTF-16LE
重新编码到
UTF-8
是不必要的,可能会影响性能。实际上,您只需要以相同的编码构建regexp:

puts File.open(
  "utf-16.txt", mode: "rb:BOM|UTF-16LE"
).readlines.grep(
  Regexp.new "foo".encode(Encoding::UTF_16LE)
)
#⇒ foo

我也在考虑大型日志文件的性能影响。我用我发现的一种稍微不同的方式编辑了答案-添加
模式:rb:BOM | UTF-16LE:UTF-8
,根据文档,它将执行以下操作:读取的字符串将在读取时由UTF-16LE标记,写入时字符串输出将转换为UTF-8。不确定“taged”是否与调用
encode
的意思相同。无论如何,我不知道你可以将Regexp字符串转换为不同的编码,所以我喜欢你的解决方案。@ViktorNonov,Regexp只是一组要直接匹配的字节+几个控制实体(都在ASCII-7中)。后面的FSM会逐字节匹配它们,这并没有什么神奇之处:)我在考虑大型日志文件的性能影响,也我用我发现的一种稍微不同的方式编辑了答案-添加
模式:rb:BOM | UTF-16LE:UTF-8
,根据文档,它将执行以下操作:读取的字符串将在读取时由UTF-16LE标记,写入时字符串输出将转换为UTF-8。不确定“taged”是否与调用
encode
的意思相同。无论如何,我不知道您可以将Regexp字符串转换为不同的编码,所以我喜欢您的解决方案。@ViktorNonov好吧,Regexp只是一组要直接匹配的字节+几个控制实体(都在ASCII-7中)。后面的FSM将逐字节匹配它们,这没有什么神奇:)