Двоичный ввод с текстовым заголовком 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()))