Python 如何预防;UnicodeDecodeError“;从sys.stdin读取管道输入时?

Python 如何预防;UnicodeDecodeError“;从sys.stdin读取管道输入时?,python,python-3.x,character-encoding,pipe,stdin,Python,Python 3.x,Character Encoding,Pipe,Stdin,我正在阅读一些主要是Python3脚本中的十六进制输入。然而,该系统 设置为使用UTF-8,当从bashshell到脚本中进行管道传输时,我保留 获取以下UnicodeDecodeError: UnicodeDecodeError:('utf-8'编解码器无法解码位置0处的字节0xed:无效的连续字节) 根据其他SO答案,我在Python3中使用读取管道输入,如下所示: 导入系统 ... isPipe=0 如果不是sys.stdin.isatty(): isPipe=1 尝试: inpipe=s

我正在阅读一些主要是Python3脚本中的十六进制输入。然而,该系统 设置为使用
UTF-8
,当从bashshell到脚本中进行管道传输时,我保留 获取以下
UnicodeDecodeError

UnicodeDecodeError:('utf-8'编解码器无法解码位置0处的字节0xed:无效的连续字节)

根据其他SO答案,我在Python3中使用读取管道输入,如下所示:

导入系统 ... isPipe=0 如果不是sys.stdin.isatty(): isPipe=1 尝试: inpipe=sys.stdin.read().strip() 除UNICEDECODEDEERROR外,错误为e: err_unicode(e) ... 使用这种方式进行管道安装时,它会起作用:

#echo“\xed\xff\xff\x0b\x04\x00\xa0\xe1”| some.py
但是,使用原始格式不会:

#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'编解码器无法解码位置0中的字节0xed:无效的连续字节)
还尝试了其他有希望的答案:

# 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
#echo-en“\xed\xff\xff\x0b\x04\x00\xa0\xe1”| python3-c“打开(1,'w')。写入(打开(0.read())”
#echo-en“\xed\xff\xff\x0b\x04\x00\xa0\xe1”| python3-c”从io导入打开;打开(1,'w')。写入(打开(0.read())”
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
文件“/usr/lib/python3.6/codecs.py”,第321行,解码中
(结果,消耗)=自身缓冲区解码(数据,自身错误,最终)
UnicodeDecodeError:“utf-8”编解码器无法解码位置0中的字节0xed:无效的连续字节
据我目前所知,当终端遇到一个序列时,后面会跟1-3个字节,如下所示:

UTF-8是一种可变宽度字符编码,能够使用一到四个8位字节对Unicode中的所有有效代码点进行编码。 因此,在 范围
0x80-0xBF

然而,我不能总是确定我的输入流来自哪里,它很可能是原始数据,而不是上面提到的ASCII十六进制版本。所以我需要以某种方式处理这些原始输入

我已经考虑了一些备选方案,如:

  • 使用

  • 使用
    open(“myfile.jpg”,“rb”,buffering=0)

  • 使用
    字节。从

  • 或者只是使用

但我不知道他们是否或如何能够像我所希望的那样读取管道输入流

如何使脚本也处理原始字节流?

是的,我读过大量类似的SO问题,但没有一个能够充分处理这个UTF-8输入错误。最好的是


这不是一个副本。

这里有一种像文件一样以二进制形式读取stdin的方法:

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

我最终通过而不是
sys.stdin
解决了这个问题

相反,我在open(0,'rb')
中使用了
。其中:

  • 0
    是等同于stdin的文件指针
  • 'rb'
    正在使用二进制模式进行读取
这似乎可以避免系统试图解释管道中的区域设置字符时出现的问题。我是在看到以下操作有效后得出这个想法的,并返回了正确的(不可打印的)字符:

echo-en“\xed\xff\xff\x0b\x04\x00\xa0\xe1”| python3-c”,打开(0,'rb')作为f:x=f.read();导入sys;sys.stdout.buffer.write(x);”
▒▒▒
▒▒
因此,为了正确读取任何管道数据,我使用了:

如果不是sys.stdin.isatty():
尝试:
打开(0,'rb')作为f:
inpipe=f.read()
例外情况除外,如e:
未知错误(e)
#这在二进制模式下不会发生:
#除UNICEDECODEDEERROR外,错误为e:
#err_unicode(e)
...
这将把管道数据读入python字节字符串

下一个问题是确定管道数据是来自字符串(如
echo“BADDATA0”
)还是来自二进制流。后者可以通过
echo-ne“\xBA\xDD\xAT\xA0”
进行模拟,如OP所示。在我的例子中,我只是使用正则表达式来查找越界的非ASCII字符。

当然,这可以做得更好、更明智。(请随意评论!)


附录:(摘自)

模式是一个可选字符串,用于指定打开文件的模式。它默认为
r
,这意味着以文本模式打开阅读。在文本模式下,如果未指定编码,则使用的编码取决于平台:
locale。调用getpreferredencoding(False)
以获取当前的locale编码。(对于读取和写入原始字节,请使用二进制模式并保留未指定的编码。)默认模式为“r”(打开以读取文本,同义词为“rt”)。对于二进制读写访问,模式
w+b
打开并将文件截断为0字节
r+b
打开文件时不截断

。。。Python区分二进制和文本I/O。以二进制模式打开的文件(包括模式参数中的
b
)将内容作为字节对象返回,而不进行任何解码。在文本模式下(默认情况下,或者当模式参数中包含
t
时),文件的内容返回为str,字节首先使用平台相关编码或指定编码(如果给定)进行解码

如果closefd
False
,并且提供了文件描述符而不是文件名,则在关闭文件时,基础文件描述符将保持打开状态。如果给定了文件名,closefd必须
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")