Как использовать 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
Вопросительные знаки в черном поле используются для замены неизвестного, неопознанного или непредставимого символа.
Вы можете проверить больше на спецификации здесь.