Как предотвратить «UnicodeDecodeError» при чтении входных данных из sys.stdin? - PullRequest
0 голосов
/ 27 октября 2018

Я читаю некоторые в основном HEX-входные данные в скрипт Python3.Однако система настроена на использование UTF-8, и при передаче из сценария Bash в сценарий я получаю следующее UnicodeDecodeError ошибка :

UnicodeDecodeError: ('utf-8' codec can't decode byte 0xed in position 0: invalid continuation byte)

Я использую sys.stdin.read() в Python3 для чтения входных данных по каналам в соответствии с другими ответами SO, например:

import sys
...
isPipe = 0
if not sys.stdin.isatty() :
    isPipe = 1
    try:
        inpipe = sys.stdin.read().strip()
    except UnicodeDecodeError as e:
        err_unicode(e)
...

Работает, когдапайпинг с использованием этого способа:

# echo "\xed\xff\xff\x0b\x04\x00\xa0\xe1" | some.py
<output all ok!>

Однако использование необработанного формата не дает:

# echo -en "\xed\xff\xff\x0b\x04\x00\xa0\xe1"

    ▒▒▒
   ▒▒

# echo -en "\xed\xff\xff\x0b\x04\x00\xa0\xe1" | some.py
UnicodeDecodeError: ('utf-8' codec can't decode byte 0xed in position 0: invalid continuation byte)

, а также пробовал другие многообещающие ответы SO:

# echo -en "\xed\xff\xff\x0b\x04\x00\xa0\xe1" | python3 -c "open(1,'w').write(open(0).read())"
# echo -en "\xed\xff\xff\x0b\x04\x00\xa0\xe1" | python3 -c "from io import open; open(1,'w').write(open(0).read())"

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python3.6/codecs.py", line 321, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xed in position 0: invalid continuation byte

Из того, что я узнал до сих пор, является то, что когда ваш терминал встречает последовательность UTF-8 , он ожидает , что за ним последуют 1-3 других байта, например:

UTF-8 - это кодировка символов переменной ширины, способная кодировать все действительные кодовые точки в Unicode с использованием от одного до четырех 8-битных байтов .Таким образом, за любым последующим байтом (первый символ UTF-8 в диапазоне 0xC2 - 0xF4) следует 1-3 байтов продолжения в диапазоне0x80 - 0xBF.

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

Я рассмотрел несколько альтернатив, таких как:

  • для использования codecs.decode

  • для использования open("myfile.jpg", "rb", buffering=0) с raw I / O

  • с использованием bytes.decode(encoding="utf-8", errors="ignore") из байтов

  • или просто с помощью open (...)

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

Как сделать так, чтобы мой скрипт обрабатывал также поток необработанных байтов?

PS.Да, я прочитал множество схожих проблем SO, но ни одна из них не имеет адекватного отношения к этой ошибке ввода UTF-8.Лучшим является этот .

Это не дубликат.

Ответы [ 2 ]

0 голосов
/ 29 октября 2018

Мне наконец-то удалось обойти эту проблему с помощью , а не , используя sys.stdin!

Вместо этого я использовал with open(0, 'rb'). Где:

  • 0 - указатель файла, эквивалентный stdin.
  • 'rb' использует двоичный режим для чтения .

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

echo -en "\xed\xff\xff\x0b\x04\x00\xa0\xe1" | python3 -c "with open(0, 'rb') as f: x=f.read(); import sys; sys.stdout.buffer.write(x);"

▒▒▒
   ▒▒

Таким образом, чтобы правильно прочитать любые данные канала, я использовал:

if not sys.stdin.isatty() :
    try:
        with open(0, 'rb') as f: 
            inpipe = f.read()

    except Exception as e:
        err_unknown(e)        
    # This can't happen in binary mode:
    #except UnicodeDecodeError as e:
    #    err_unicode(e)
...

Это будет читать ваши данные канала в строку байта Python .

Следующая проблема состояла в том, чтобы определить, поступали ли данные канала из строки символов (например, echo "BADDATA0") или из двоичного потока . Последний может быть эмулирован echo -ne "\xBA\xDD\xAT\xA0", как показано в OP. В моем случае я просто использовал RegEx для поиска за пределами не ASCII символов.

if inpipe :
    rx = re.compile(b'[^0-9a-fA-F ]+') 
    r = rx.findall(inpipe.strip())
    if r == [] :
        print("is probably a HEX ASCII string")
    else:
        print("is something else, possibly binary")

Конечно, это можно сделать лучше и умнее. (Не стесняйтесь комментировать!)


Приложение: (от здесь )

mode - необязательная строка, определяющая режим, в котором открывается файл. По умолчанию r, что означает открытый для чтения в текстовом режиме. В текстовом режиме , если кодировка не указана, используемая кодировка зависит от платформы: locale.getpreferredencoding(False) вызывается для получения текущей кодировки локали. (Для чтения и записи необработанных байтов используйте двоичный режим и оставьте кодировку неуказанной.) Режим по умолчанию - «r» (открыт для чтения текста, синоним «rt»). Для двоичного доступа для чтения и записи режим w+b открывается и усекает файл до 0 байтов. r+b открывает файл без усечения.

... Python различает двоичный и текстовый ввод / вывод. Файлы, открытые в двоичном режиме (включая b в аргументе mode), возвращают содержимое в виде байтовых объектов без какого-либо декодирования. В текстовом режиме (по умолчанию или когда t включен в аргумент режима) содержимое файла возвращается как str , причем байты были сначала декодированы с использованием зависящего от платформы кодирования или с использованием указанная кодировка, если дана.

Если closefd равен False и был задан дескриптор файла, а не имя файла, базовый дескриптор файла будет оставаться открытым при закрытии файла. Если указано имя файла, closefd должно быть True (по умолчанию), иначе возникнет ошибка.

0 голосов
/ 28 октября 2018

Вот хакерский способ чтения стандартного ввода в двоичном виде, как файл:

import sys

with open(sys.stdin.fileno(), mode='rb', closefd=False) as stdin_binary:
    raw_input = stdin_binary.read()
try:
    # text is the string formed by decoding raw_input as unicode
    text = raw_input.decode('utf-8')
except UnicodeDecodeError:
    # raw_input is not valid unicode, do something else with it
...