Popen.communicate() 抛出 UnicodeDecodeError

4

我有这段代码:

def __executeCommand(self, command: str, input: str = None) -> str:
    p = sub.Popen(command, stdout=sub.PIPE, stderr=sub.PIPE, stdin=sub.PIPE, universal_newlines=True)
    p.stdin.write(input)
    output, error = p.communicate()
    if (len(errors) > 0):
        raise EnvironmentError("Could not generate the key: " + error)
    elif (p.returncode != 0):
        raise EnvironmentError("Could not generate the key. Return Value: " + p.returncode)
    return output

我在代码中的这一行 output, error = p.communicate() 遇到了一个 UnicodeDecodeError 的错误:

Traceback (most recent call last):
  File "C:\Python34\lib\threading.py", line 921, in _bootstrap_inner
    self.run()
  File "C:\Python34\lib\threading.py", line 869, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Python34\lib\subprocess.py", line 1170, in _readerthread
    buffer.append(fh.read())
  File "C:\Python34\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x81 in position 27: character maps to <undefined>

我该如何修复这个问题?

不相关:您不需要使用 p.stdin.write(input);而是应该使用 ... = p.communicate(input) - jfs
4个回答

5
univeral_newlines=True启用文本模式。子进程输出(字节)使用 locale.getpreferredencoding(False) 字符编码进行解码,如@cdosborn提到的
如果不起作用,请提供实际使用的encoding。或者指定错误处理程序,例如'ignore','surrogateescape'等,作为errors参数:
from subprocess import Popen, PIPE

def __executeCommand(self, command: str, input: str = None, 
                     encoding=None, errors='strict') -> str:
    text_mode = (encoding is None)
    with Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE,
               universal_newlines=text_mode) as p:
        if input is not None and not text_mode:
            input = input.encode(encoding, errors) # convert to bytes
        output, err = p.communicate(input)
    if err or p.returncode != 0: 
        raise EnvironmentError("Could not generate the key. "
                               "Error: {}, Return Value: {}".format(
                                   ascii(err), p.returncode))
    return output if text_mode else output.decode(encoding, errors)

1
我没有找到正确的编码方式... 但是只有错误提示信息包含特殊字符时,使用output.decode(encoding, errors='replace')才对我有用。 - habakuk
Python应该是一种脚本语言...但感觉像最糟糕的C语言... - Christopher Oezbek
@ChristopherOezbek: 如果您要在任何编程语言中实现相同的功能,您将如何实现?(可能会有一个问题,关于是否将功能打包到单个函数中是否合理,但与Python无关:您可以使用任何语言编写FORTRAN) - jfs
@J.F. Sebastian:抱歉我的沮丧,我从未使用过这样的脚本语言,让我担心那么多事情(编码,换行符),当我只想要简单的反引号操作行为时:运行一个命令,获取stdout和stderr作为字符串以便再次打印到stdout。 - Christopher Oezbek
1
@ChristopherOezbek:如果你需要“反引号”,请使用output = subprocess.check_output(command)。我不明白它与问题或Python有什么关系:如果你正在处理文本,你应该了解编码(没有所谓的纯文本)。 - jfs
显示剩余2条评论

3
如果您使用的是Python 3.6或更高版本,您可以通过更改此行来修复错误:
p = sub.Popen(command, stdout=sub.PIPE, stderr=sub.PIPE, stdin=sub.PIPE, universal_newlines=True)

转换为:

p = sub.Popen(command, stdout=sub.PIPE, stderr=sub.PIPE, stdin=sub.PIPE, encoding="utf-8", universal_newlines=True)

我在上面使用了UTF-8,但是您可以用任何需要的编码替换它。


2

universal_newlines=true 设置会导致额外的编码,这可能是您错误的源头。

def __executeCommand(self, command: str, input: str = None) -> str:
    p = sub.Popen(command, stdout=sub.PIPE, stderr=sub.PIPE, stdin=sub.PIPE)
    output, error = p.communicate(input)
    if (len(errors) > 0):
        raise EnvironmentError("Could not generate the key: " + error)
    elif (p.returncode != 0):
        raise EnvironmentError("Could not generate the key. Return Value: " + p.returncode)
    return output

universal_newlines=true会生成一种基于以下输出的编码:

python -c 'import locale; print locale.getpreferredencoding()'

当Python期望您的输入与上述编码匹配,但实际处理了一个明显使用不同编码的字节时,Python会抛出错误。

有关Python 3.4 universal_newlines的更多信息,请单击此处


1
注意:函数签名中的 -> str。如果您删除 universal_newlines=True,则 subprocess 以二进制模式工作,即结果为 bytes 而不是 str。 您必须手动解码(您需要知道 command 使用的字符编码来解码其输出)。并且您还必须对 input 进行编码。 - jfs
我认为必须在上一层做出假设。无法预先知道每个“command”输入或输出的编码方式。据我所知,类型字节和类型字符串之间没有区别。可选地,输出可以包装为str(output, locale.getpreferredencoding()) - cdosborn
包装,就像我之前提到的那样,也很可能会引发上述错误。 - cdosborn
请注意,OP使用的是Python 3:str(Unicode文本)与bytes类型(二进制类型)非常不同。 - jfs
使用“locale”编码来包装不会起到作用,否则“universal_newlines”就可以正常工作了。原文提供者应该提供正确的编码方式和/或使用“errors”处理程序(通过“bytestring.decode(encoding,errors)”或通过“io.TextIOWrapper(p.stdout,encoding,errors)”)。 - jfs

0

我的一个用户在Windows上运行了我的代码,该代码运行了tasklist命令。为了解决这个错误,我不得不使用iso-8859-2编码。

p = sub.Popen(cmd, stdout=sub.PIPE, stdin=sub.PIPE, stderr=sub.PIPE,
              text=True, encoding='iso-8859-2')

您可以加入更多有关代码作用及其如何帮助提问者的信息来改进您的答案。 - Tyler2P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接