Как использовать Ruby's readlines.grep для файлов 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, так же, как в UTF-8 с использованием Ruby.

Вот рабочий пример для файла 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)

Я пытался конвертировать файл на основе этого поста с помощью:

$ ruby -e 'puts File.open("utf-16.txt", "r").read.force_encoding("ISO-8859-1").encode("utf-8", replace: nil)' 
ÿþfoo
bar
baz

но он печатает некоторые недопустимые символы (ÿþ) до foo во-вторых, я не знаю, как использовать grep метод после преобразования (он сообщает как неопределенный метод).

Как я могу использовать readlines.grep() метод для файла UTF-16? Или каким-то другим простым способом, где моя цель - напечатать строки с определенным шаблоном регулярных выражений.


Идеально в одну строку, поэтому команда может использоваться для тестов CI.

Вот сценарий реального мира:

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

но команда не работает из-за UTF-16 форматирования файла журнала.

2 ответа

Решение

Хотя ответ Виктора технически верен, перекодировка всего файла из UTF-16LE в UTF-8 является ненужным и может повлиять на производительность. Все, что вам действительно нужно, это построить регулярное выражение в той же кодировке:

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

Короткий ответ:

Это почти у вас есть, просто нужно сказать, какие символы вы хотите заменить (я бы предположил, что недопустимый и неопределенный):

$ ruby -e 'puts File.open("utf-16.txt", "r").read.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")'
foo
bar
baz

Также я не думаю, что вам нужно force_encoding,

Если вы хотите игнорировать 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

заключается в том, что в начале каждого файла, который находится в Юникоде, вы можете иметь метку порядка байтов, которая показывает порядок байтов и форму кодирования. В вашем случае это FE FF (имеется в виду Little-endian UTF-16), которые являются недопустимыми символами UTF-8.

Вы можете проверить это, вызвав encode без force_encoding:

$ruby -e 'puts File.open("utf-16.txt", "r").read.encode("utf-8")'
��foo
bar
baz

Вопросительные знаки в черном поле используются для замены неизвестного, неопознанного или непредставимого символа.

Вы можете проверить больше на спецификации здесь.

Другие вопросы по тегам