Кодировка Ruby ASCII_8BIT и расширенный ASCII

О ASCII_8BIT

Encoding :: ASCII_8BIT - это специальная кодировка, которая обычно используется для байтовой строки, а не для символьной строки. Но, как следует из названия, его символы в диапазоне ASCII считаются символами ASCII. Это полезно, когда вы используете символы ASCII-8BIT с другими совместимыми символами ASCII.

Источник: ruby-doc.org/core-2.6.4

Контекст

Я хочу использовать ASCII_8BIT, потому что мне нужно кодировать все символы от 0x00 (0d00) до 0xff (0d255), поэтому ASCII (0-127) плюс расширенный ASCII (128-255). ASCII (кодировка, US-ASCII) - это 7-битная кодировка, которая распознает только символы ASCII (кодировка) (0–127). Как видно из названия, я ожидал, что ASCII_8BIT расширит его до 8 бит, чтобы добавить поддержку 128–255.

Проблема

Когда я использую chr, кодировка автоматически устанавливается на ASCII_8BIT, но когда я помещаю char между 128-255 (0x80-0xff) непосредственно в строку, а затем спрашиваю, какая кодировка я получил вместо этого UTF-8, и если я попытаюсь преобразовать его в ASCII_8BIT - получить ошибку.

irb(main):014:0> 0x8f.chr
=> "\x8F"
irb(main):015:0> 0x8f.chr.encoding
=> #<Encoding:ASCII-8BIT>
irb(main):016:0> "\x8f".encode(Encoding::ASCII_8BIT)
Traceback (most recent call last):
        5: from /usr/bin/irb:23:in `<main>'
        4: from /usr/bin/irb:23:in `load'
        3: from /usr/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        2: from (irb):16
        1: from (irb):16:in `encode'
Encoding::InvalidByteSequenceError ("\x8F" on UTF-8)
irb(main):021:0> "\x8F".encoding
=> #<Encoding:UTF-8>

Есть ли ошибка в ядре Ruby? Мне нужно уметь кодировать все между 8

Другое имя ASCII 8BIT - BINARY, потому что, как указано в предыдущей цитате, он должен иметь возможность кодировать любой байт.

irb(main):035:0> Encoding::ASCII_8BIT.names
=> ["ASCII-8BIT", "BINARY"]

Другие кодировки

Пожалуйста, скажите мне использовать другую кодировку, это не ответ на вопрос, если только это не кодировка, которая действительно отображает все 255 расширенных символов ASCII.

  • Я не хочу использовать UTF-8, потому что кодировка многобайтовая, а не однобайтовая.
  • ISO / IEC 8859-1 (Latin1, 8bits) содержит только 191 символ (ASCII + 63 символа)
    # P8 #
  • Windows-1252 (CP-1252, 8 бит) не содержит всех 255 символов и в виде различных сопоставлений, которые расширены ASCII

Доступные кодировки в рубине:

irb(main):036:0> Encoding.name_list
=> ["ASCII-8BIT", "UTF-8", "US-ASCII", "UTF-16BE", "UTF-16LE", "UTF-32BE", "UTF-32LE", "UTF-16", "UTF-32", "UTF8-MAC", "EUC-JP", "Windows-31J", "Big5", "Big5-HKSCS", "Big5-UAO", "CP949", "Emacs-Mule", "EUC-KR", "EUC-TW", "GB2312", "GB18030", "GBK", "ISO-8859-1", "ISO-8859-2", "ISO-8859-3", "ISO-8859-4", "ISO-8859-5", "ISO-8859-6", "ISO-8859-7", "ISO-8859-8", "ISO-8859-9", "ISO-8859-10", "ISO-8859-11", "ISO-8859-13", "ISO-8859-14", "ISO-8859-15", "ISO-8859-16", "KOI8-R", "KOI8-U", "Shift_JIS", "Windows-1250", "Windows-1251", "Windows-1252", "Windows-1253", "Windows-1254", "Windows-1257", "BINARY", "IBM437", "CP437", "IBM737", "CP737", "IBM775", "CP775", "CP850", "IBM850", "IBM852", "CP852", "IBM855", "CP855", "IBM857", "CP857", "IBM860", "CP860", "IBM861", "CP861", "IBM862", "CP862", "IBM863", "CP863", "IBM864", "CP864", "IBM865", "CP865", "IBM866", "CP866", "IBM869", "CP869", "Windows-1258", "CP1258", "GB1988", "macCentEuro", "macCroatian", "macCyrillic", "macGreek", "macIceland", "macRoman", "macRomania", "macThai", "macTurkish", "macUkraine", "CP950", "Big5-HKSCS:2008", "CP951", "IBM037", "ebcdic-cp-us", "stateless-ISO-2022-JP", "eucJP", "eucJP-ms", "euc-jp-ms", "CP51932", "EUC-JIS-2004", "EUC-JISX0213", "eucKR", "eucTW", "EUC-CN", "eucCN", "GB12345", "CP936", "ISO-2022-JP", "ISO2022-JP", "ISO-2022-JP-2", "ISO2022-JP2", "CP50220", "CP50221", "ISO8859-1", "ISO8859-2", "ISO8859-3", "ISO8859-4", "ISO8859-5", "ISO8859-6", "Windows-1256", "CP1256", "ISO8859-7", "ISO8859-8", "Windows-1255", "CP1255", "ISO8859-9", "ISO8859-10", "ISO8859-11", "TIS-620", "Windows-874", "CP874", "ISO8859-13", "ISO8859-14", "ISO8859-15", "ISO8859-16", "CP878", "MacJapanese", "MacJapan", "ASCII", "ANSI_X3.4-1968", "646", "UTF-7", "CP65000", "CP65001", "UTF-8-MAC", "UTF-8-HFS", "UCS-2BE", "UCS-4BE", "UCS-4LE", "CP932", "csWindows31J", "SJIS", "PCK", "CP1250", "CP1251", "CP1252", "CP1253", "CP1254", "CP1257", "UTF8-DoCoMo", "SJIS-DoCoMo", "UTF8-KDDI", "SJIS-KDDI", "ISO-2022-JP-KDDI", "stateless-ISO-2022-JP-KDDI", "UTF8-SoftBank", "SJIS-SoftBank", "locale", "external", "filesystem", "internal"]

Для сравнения кодировок python https://docs.python.org/3/library/codecs.html#standard-encodings

Соображения

Читая Extended ASCII - многобайтовые кодировки символов, кажется, что единственно верный расширенная кодировка ASCII - UTF-8, но многобайтовая. Похоже, что истинной расширенной однобайтовой кодировки ASCII тоже не существует.

С байтовой точки зрения я мог бы использовать любую 8-битную (однобайтовую) кодировку, как сказано здесь Extended ASCII - использование в машиночитаемых языках

все байты ASCII (от 0x00 до 0x7F) имеют одинаковое значение во всех вариантах расширенного ASCII,

Но проблема в том, что такие реализации, как ISO-8859-1, специально не определили некоторые диапазоны символов и, таким образом, завершатся ошибками.

irb(main):009:0> (0..255).map { |c| c.chr}.join.encode(Encoding::ISO_8859_1)
Traceback (most recent call last):
        6: from /usr/bin/irb:23:in `<main>'
        5: from /usr/bin/irb:23:in `load'
        4: from /usr/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        3: from (irb):9
        2: from (irb):9:in `rescue in irb_binding'
        1: from (irb):9:in `encode'
Encoding::UndefinedConversionError ("\x80" to UTF-8 in conversion from ASCII-8BIT to UTF-8 to ISO-8859-1)

Обновление - force_encoding

Я нашел строковый метод force_encoding.

irb(main)> a = "\x8f"
=> "\x8F"
irb(main)> a.encoding
=> #<Encoding:UTF-8>
irb(main)> a.encode(Encoding::ASCII_8BIT)
Traceback (most recent call last):
        5: from /usr/bin/irb:23:in `<main>'
        4: from /usr/bin/irb:23:in `load'
        3: from /usr/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        2: from (irb):42
        1: from (irb):42:in `encode'
Encoding::InvalidByteSequenceError ("\x8F" on UTF-8)
irb(main)> a.force_encoding(Encoding::ASCII_8BIT)
=> "\x8F"
irb(main):040:0> a.encoding
=> #<Encoding:ASCII-8BIT>

В чем опасность использования force_encoding вместо encode? Просто если я случайно передаю многобайтовый символ, он будет преобразован в несколько однобайтовых символов? Так что не опасно, если есть уверенность, что все символы, переданные в приложение, находятся в расширенном диапазоне ASCII (однобайтный), но небезопасны и вызовут проблему беззвучного преобразования, если, например, приложению будут переданы некоторые символы UTF-8.

irb(main):044:0> "\ud087".force_encoding(Encoding::ASCII_8BIT)
=> "\xED\x82\x87"
irb(main):045:0> "\ud087".bytes
=> [237, 130, 135]

Обновление - Ответ

Ответ @ mu-is-too-short и комментарий @ ForeverZer0 предполагают, что мне лучше использовать pack и unpack для работы с необработанными байтами.

Поэтому вместо того, чтобы использовать кодировку и обходное решение с ней

pattern = 'A' * 2606 + "\x8F\x35\x4A\x5F" + 'C' * 390
pattern.force_encoding(Encoding::ASCII_8BIT)

Я должен использовать байты напрямую

pattern = ['A'.ord] * 2606 + [0x8F, 0x35, 0x4A, 0x5F] + ['C'.ord] * 390
pattern = pattern.pack('C*')

Или этот более легкий для чтения синтаксис

pattern = 'A'.bytes * 2606 + "\x8F\x35\x4A\x5F".bytes + 'C'.bytes * 390
pattern = pattern.pack('C*')

person noraj    schedule 18.09.2019    source источник
comment
С какими данными вы имеете дело? ASCII_8BIT на самом деле не кодировка, это скорее некодирование, и здесь нет расширенных символов ASCII, они формально не определен. DOS ANSI (кодовая страница 437) - одна из множества 8-битных кодировок, как и латинская -1, Windows-1252 и т. Д. В каком формате находятся ваши исходные данные? Если вы имеете дело с необработанными двоичными данными, ответ будет BINARY, который по умолчанию преобразуется в ASCII_8BIT, или, другими словами, он сохраняет байты и не выполняет преобразование.   -  person tadman    schedule 19.09.2019
comment
@tadman Ни один из них. Я манипулирую необработанным TCP-сокетом для какого-то сетевого протокола и хочу отправить необработанные байты. Поэтому мне нужна была правильная расширенная кодировка ASCII, чтобы быть уверенным, что когда я отправляю 0x8f или что-то еще, я действительно отправляю 0x8f, а не несколько байтов, поскольку я могу быть в случае использования UTF-8 (который используется по умолчанию, когда я предоставляю расширенный Символы ASCII в строке) или в любой другой многобайтовой кодировке.   -  person noraj    schedule 19.09.2019
comment
force_encoding не делает ничего, кроме как вынуждает Ruby интерпретировать одни и те же точные данные по-разному, все данные остаются такими же, просто на них смотрят по-разному. encode фактически преобразует данные и возвращает другие данные.   -  person ForeverZer0    schedule 19.09.2019
comment
@ ForeverZer0 Итак, пока что лучший вариант, кажется, использовать .force_encoding(Encoding::ASCII) или force_encoding(Encoding::ASCII_8BIT), чтобы обязательно отправлять необработанные байты, а не отправлять преобразованные многобайты, например, если в противном случае кодировка была бы автоматически установлена ​​на UTF-8.   -  person noraj    schedule 19.09.2019
comment
@noraj Лучший способ добиться этого - просто использовать pack и unpack, чтобы убедиться, что вы получаете необработанные двоичные данные, и не пытаетесь использовать кодировку. Ruby, использующий строки для представления необработанных данных, обычно удобен, но может сделать некоторые варианты использования, такие как ваш, немного более тонкими.   -  person ForeverZer0    schedule 19.09.2019


Ответы (2)


Строковые литералы (обычно) кодируются в кодировке UTF-8 независимо от того, являются ли байты допустимыми UTF-8. Отсюда это:

"\x8f".encoding

говоря UTF-8, даже если строка недействительна UTF-8. Вы должны быть в безопасности, используя String#force_encoding, но если вы действительно хотите работать с необработанными байтами, возможно, вам лучше работать с массивами целых чисел и использовать Array#pack, чтобы объединить их в строки:

[ 0x8f, 0x11, 0x06, 0x23, 0xff, 0x00 ].pack('C*')
# "\x8F\x11\x06#\xFF\x00" 
[ 0x8f, 0x11, 0x06, 0x23, 0xff, 0x00 ].pack('C*').encoding
# #<Encoding:ASCII-8BIT> 
[ 0x8f, 0x11, 0x06, 0x23, 0xff, 0x00 ].pack('C*').bytes
# [143, 17, 6, 35, 255, 0] 

Результаты должны быть такими же, но, IMO, это явно работает с двоичными данными (т.е. необработанными байтами), проясняет ваше намерение и должно избегать любых проблем с кодированием.

Также есть String#unpack, если существует известная структура байтов. вы читаете и хотите его открыть.

person mu is too short    schedule 18.09.2019
comment
Поэтому вместо использования ruby pattern = 'A' * 2606 + "\x8F\x35\x4A\x5F" + 'C' * 390; pattern.force_encoding(Encoding::ASCII) я должен использовать ruby pattern = ['A'.ord] * 2606 + [0x8F, 0x35, 0x4A, 0x5F] + ['C'.ord] * 390; pattern.pack('C*') - person noraj; 19.09.2019
comment
Это в основном дело вкуса (но я бы сказал force_encoding('binary') просто для ясности), но если бы я работал с байтами, я бы использовал pack. - person mu is too short; 19.09.2019

Если вы выполняете манипуляции с необработанными пакетами, тогда все должно быть в режиме BINARY / ASCII_8BIT, потому что это не текст и не должно рассматриваться как таковое. Если у вас есть кодировка, отличная от этой, Ruby попытается ее преобразовать, что в лучшем случае сильно испортит двоичные данные, а в худшем - выйдет из строя из-за ошибок преобразования.

В терминах Ruby ASCII_8BIT фактически является буфером необработанных данных.

Кодировка по умолчанию для строк в вашем коде - UTF-8:

p "example".encoding
# => #<Encoding:UTF-8>

Вы можете установить кодировку Ruby для встроенных строк для каждого файла с помощью # encoding: BINARY:

# encoding: BINARY

p "example".encoding
# => #<Encoding:ASCII-8BIT>

Обычно двоичные данные лучше выражать с помощью таких инструментов, как pack, как указывает mu, когда вы не можете ошибиться и на самом деле вы вообще не используете строки. Это вдвойне важно, потому что 8-битные значения легко обрабатывать, но 16- и 32-битные значения должны быть правильно закодированы с порядком байтов, поэтому вы часто увидите много такого:

header = pack('nn', qtype, qclass)

Где это составляет заголовок DNS, который включает два 16-битных значения.

person tadman    schedule 19.09.2019