Двоичный ввод с текстовым заголовком ASCII, прочитанный из стандартного ввода

Я хочу прочитать двоичный файл PNM- изображения со стандартного ввода. Файл содержит заголовок, который закодирован как текст ASCII, и полезную нагрузку, которая является двоичной. В качестве упрощенного примера чтения заголовка я создал следующий фрагмент:

#! /usr/bin/env python3
import sys
header = sys.stdin.readline()
print("header=["+header.strip()+"]")

Я запускаю его как "test.py" (из оболочки Bash), и он отлично работает в этом случае:

$ printf "P5 1 1 255\n\x41" |./test.py 
header=[P5 1 1 255]

Однако небольшое изменение в бинарной полезной нагрузке нарушает его:

$ printf "P5 1 1 255\n\x81" |./test.py 
Traceback (most recent call last):
  File "./test.py", line 3, in <module>
    header = sys.stdin.readline()
  File "/usr/lib/python3.4/codecs.py", line 313, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x81 in position 11: invalid start byte

Есть ли простой способ заставить это работать в Python 3?

2 ответа

Решение

Из документов можно читать двоичные данные (как тип bytes) из стандартного с sys.stdin.buffer.read():

Для записи или чтения двоичных данных из / в стандартные потоки используйте базовый объект двоичного буфера. Например, чтобы записать байты в stdout, используйте sys.stdout.buffer.write(b'abc').

Так что это одно направление, которое вы можете выбрать - читать данные в двоичном режиме. readline() и различные другие функции все еще работают. После захвата строки ASCII ее можно преобразовать в текст, используя decode('ASCII'), для дополнительной обработки текста.

Кроме того, вы можете использовать io.TextIOWrapper() указать на использование latin-1 набор символов в потоке ввода. При этом неявная операция декодирования, по сути, будет проходной, поэтому данные будут иметь тип str (которые представляют текст), но данные представлены с помощью преобразования 1-в-1 из двоичного файла (хотя может использоваться более одного байта памяти на входной байт).

Вот код, который работает в любом режиме:

#! /usr/bin/python3

import sys, io

BINARY=True ## either way works

if BINARY: istream = sys.stdin.buffer
else:      istream = io.TextIOWrapper(sys.stdin.buffer,encoding='latin-1')

header = istream.readline()
if BINARY: header = header.decode('ASCII')
print("header=["+header.strip()+"]")

payload = istream.read()
print("len="+str(len(payload)))
for i in payload: print( i if BINARY else ord(i) )

Проверьте каждую возможную 1-пиксельную полезную нагрузку с помощью следующей команды Bash:

for i in $(seq 0 255) ; do printf "P5 1 1 255\n\x$(printf %02x $i)" |./test.py ; done

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

#!/usr/bin/env python3
import sys

sys.stdin = sys.stdin.detach() # convert to binary stream
header = sys.stdin.readline().decode('ascii') # b'\n'-terminated
print(header, end='')
print(repr(sys.stdin.read()))
Другие вопросы по тегам