带有 ASCII 文本 header 的二进制输入,从标准输入读取

binary input with an ASCII text header, read from stdin

我想从标准输入读取二进制 PNM 图像文件。该文件包含一个编码为 ASCII 文本的 header 和一个二进制有效负载。作为阅读 header 的简化示例,我创建了以下片段:

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

我 运行 它作为 "test.py" (来自 Bash shell),在这种情况下它工作正常:

$ 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 中完成这项工作?

the docs 开始,可以使用 sys.stdin.buffer.read():

从标准输入读取二进制数据(类型 bytes

To write or read binary data from/to the standard streams, use the underlying binary buffer object. For example, to write bytes to stdout, use sys.stdout.buffer.write(b'abc').

所以这是您可以采取的一个方向 -- 以二进制模式读取数据。 readline() 和其他各种功能仍然有效。捕获 ASCII 字符串后,可以使用 decode('ASCII') 将其转换为文本,以进行其他特定于文本的处理。

或者,您可以使用 io.TextIOWrapper() 来指示在输入流上使用 latin-1 字符集。有了这个,隐式解码操作本质上将是一个传递操作——因此数据将是 str 类型(代表文本),但数据是用来自二进制(尽管每个输入字节可能使用多个存储字节)。

以下代码适用于任一模式:

#! /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) )

使用以下 Bash 命令测试每个可能的 1 像素负载:

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

要读取二进制数据,您应该使用二进制流,例如,使用 TextIOBase.detach() method:

#!/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()))