我正在使用这段代码从一个外部程序中获得标准输出。
>>> from subprocess import *
>>> command_stdout = Popen(['ls', '-l'], stdout=PIPE).communicate()[0]
communicate()方法返回一个字节数组。
>>> command_stdout
b'total 0\n-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file1\n-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file2\n'
然而,我想把输出作为一个正常的Python字符串来处理。这样我就可以像这样打印它。
>>> print(command_stdout)
-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file1
-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file2
我想这就是 binascii.b2a_qp() 方法的作用,但当我尝试时,我又得到了同样的字节数组。
>>> binascii.b2a_qp(command_stdout)
b'total 0\n-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file1\n-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file2\n'
我如何将字节值转换回字符串?我是说,使用"电池",而不是手动操作。而且我希望它在Python 3中可以正常使用。
如果你不知道编码,那么要用Python 3和Python 2兼容的方式将二进制输入读成字符串,请使用古老的MS-DOS [CP437][1]编码。
PY3K = sys.version_info >= (3, 0)
lines = []
for line in stream:
if not PY3K:
lines.append(line)
else:
lines.append(line.decode('cp437'))
因为编码是未知的,所以希望非英文符号能翻译成cp437
的字符(英文字符没有被翻译,因为它们在大多数单字节编码和UTF-8中是匹配的)。
将任意二进制输入解码为UTF-8是不安全的,因为你可能会得到这样的结果。
>>> b'\x00\x01\xffsd'.decode('utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 2: invalid
start byte
同样的情况也适用于latin-1
,它在Python 2中很流行(默认?)。
请看 [Codepage Layout][2]中的缺失点--这就是Python用臭名昭著的 "ordinal not in range "扼杀的地方。
更新20150604。
有传言说Python 3有surrogateescape
错误策略,可以把东西编码成二进制数据,不会出现数据丢失和崩溃,但需要转换测试,[二进制] -> [str] -> binary]
,来验证性能和可靠性。
UPDATE 20170116:
感谢Nearoo的评论--还可以用backslashreplace
错误处理程序对所有未知字节进行斜线转义。
这只适用于Python 3,所以即使有了这个变通方法,你仍然会从不同的Python版本得到不一致的输出。
PY3K = sys.version_info >= (3, 0)
lines = []
for line in stream:
if not PY3K:
lines.append(line)
else:
lines.append(line.decode('utf-8', 'backslashreplace'))
详情请参见[Python的Unicode支持][3]。
更新 20170119。
我决定实现对Python 2和Python 3都适用的斜线转义解码。
它应该比cp437
方案慢,但它应该在每个Python版本上产生相同的结果。
# --- preparation
import codecs
def slashescape(err):
""" codecs error handler. err is UnicodeDecode instance. return
a tuple with a replacement for the unencodable part of the input
and a position where encoding should continue"""
#print err, dir(err), err.start, err.end, err.object[:err.start]
thebyte = err.object[err.start:err.end]
repl = u'\\x'+hex(ord(thebyte))[2:]
return (repl, err.end)
codecs.register_error('slashescape', slashescape)
# --- processing
stream = [b'\x80abc']
lines = []
for line in stream:
lines.append(line.decode('utf-8', 'slashescape'))
[1]: https://en.wikipedia.org/wiki/Code_page_437 [2]: https://en.wikipedia.org/wiki/ISO/IEC_8859-1#Codepage_layout [3]: https://docs.python.org/3/howto/unicode.html#python-s-unicode-support
在Python 3]1中,默认编码是"utf-8"
,所以可以直接使用。
b'hello'.decode()
等于
b'hello'.decode(encoding="utf-8")
另一方面,[在Python 2][2]中,编码默认为默认的字符串编码。 因此,您应该使用。
b'hello'.decode(encoding)
其中 encoding
是你想要的编码。
[注:][2] 在Python 2.7中增加了对关键字参数的支持。
1:
[2]: https://docs.python.org/2.7/library/stdtypes.html#str.decode
我认为你实际上想要这个。
>>> from subprocess import *
>>> command_stdout = Popen(['ls', '-l'], stdout=PIPE).communicate()[0]
>>> command_text = command_stdout.decode(encoding='windows-1252')
Aaron'的回答是正确的,只是你需要知道使用哪种编码。而我相信Windows使用的是'windows-1252'。只有当你的内容中有一些不寻常的(非ASCII)字符时才会有影响,但那时就会有区别。
顺便说一下,它确实重要的事实是Python转向使用两种不同类型的二进制和文本数据的原因:它不能在它们之间神奇地转换,因为除非你告诉它,否则它不知道编码!你知道的唯一方法是在你的文件中加入一个新编码。你知道的唯一方法是阅读Windows文档(或在这里阅读)。
虽然[@Aaron Maenpaa'的回答][1]只是有效,但有用户[最近问][2]。
有没有更简单的方法? 'fhand.read().decode("ASCII")'。 [...]好长啊!!!!!!!!!!!!!!!!!!!!!!!!!!!)。
你可以使用。
command_stdout.decode()
decode()
有一个[标准参数][3]。
codecs.decode(obj, encoding='utf-8', errors='strict')
。
[1]: https://stackoverflow.com/a/33688948/1587329 [2]: https://stackoverflow.com/questions/33688837/urllib-for-python-3/33688948#comment55151210_33688948 [3]: https://docs.python.org/3/library/stdtypes.html#bytes-and-bytearray-operations
由于这个问题实际上是问 "子进程 "的输出,你有一个更直接的方法,因为Popen
接受一个 encoding 关键字 (在 Python 3.6+ 中)。
>>> from subprocess import Popen, PIPE
>>> text = Popen(['ls', '-l'], stdout=PIPE, encoding='utf-8').communicate()[0]
>>> type(text)
str
>>> print(text)
total 0
-rw-r--r-- 1 wim badger 0 May 31 12:45 some_file.txt
其他用户的一般答案是解字节为文本。
>>> b'abcde'.decode()
'abcde'
如果没有参数,将使用sys.getdefaultencoding()
。
如果您的数据不是sys.getdefaultencoding()
,那么您必须在decode
调用中明确指定编码。
>>> b'caf\xe9'.decode('cp1250')
'café'
要将一个字节序列解释为文本,你必须知道的是 对应的字符编码。
unicode_text = bytestring.decode(character_encoding)
例子:
>>> b'\xc2\xb5'.decode('utf-8')
'µ'
>>> b'\xc2\xb5'.decode('utf-8')
'µ'
ls
命令可能产生无法解释为文本的输出。
文件名
在Unix上,除了斜杠b'/'
和零之外,可以是任何字节序列。
b'\0'
:
>>> open(bytes(range(0x100)).translate(None, b'\0/'), 'w').close()
试图使用utf-8编码对这种字节汤进行解码会引起UnicodeDecodeError
。
更糟糕的情况是。 解码可能会无声无息地失败,并产生mojibake 如果您使用了错误的不兼容编码。
>>> '—'.encode('utf-8').decode('cp1252')
'—'
数据被破坏了,但你的程序仍然不知道发生了故障。 已发生。
一般来说,使用什么样的字符编码并不是嵌入在字节序列本身。
你必须在带外传达这些信息。
有些结果比其他结果更有可能发生,因此存在chardet
模块,可以*猜测字符编码。
一个Python脚本可能在不同的地方使用多种字符编码。
ls
输出可以使用[os.fsdecode()
转换为Python字符串。
函数](),即使是[不可解码的]也能成功。
文件名](https://www.python.org/dev/peps/pep-0383/)(它使用的是
sys.getfilesystemencoding()
和surrogateescape
错误处理程序在
Unix)。)
import os
import subprocess
output = os.fsdecode(subprocess.check_output('ls'))
要获得原始字节,可以使用os.fsencode()
。
如果你传递了universal_newlines=True
参数,那么subprocess
就会使用
locale.getpreferredencoding(False)
来对字节进行解码,例如,它可以是
cp1252
在Windows上。
要对字节流进行即时解码。
io.TextIOWrapper()
可以使用。
example。
不同的命令可能使用不同的字符编码来表示它们的
输出,如dir
内部命令(cmd
)可以使用cp437。
要对其进行解码
输出,你可以显式地传递编码 (Python 3.6+)。
output = subprocess.check_output('dir', shell=True, encoding='cp437')
文件名可能与os.listdir()
不同(它使用Windows
Unicode API)例如,'\xb6'
可以用'\x14'
-Python's代替。
cp437编解码器将b'\x14'
映射到控制字符U+0014,而非
U+00B6 (¶)。
要支持使用任意Unicode字符的文件名,请参阅https://stackoverflow.com/q/33936074/4279。
当处理来自Windows系统的数据时(行尾),我的答案是
String = Bytes.decode("utf-8").replace("\r\n", "\n")
为什么? 用多行输入法试试。
Bytes = open("Input.txt", "rb").read()
String = Bytes.decode("utf-8")
open("Output.txt", "w").write(String)
你的所有行尾都会被加倍 (变成\r\rn
),导致额外的空行。
Python'的文本读取函数通常会对行尾进行规范化处理,使字符串只使用\n
。
如果你从 Windows 系统接收二进制数据,Python 没有机会这样做。
因此。
Bytes = open("Input.txt", "rb").read()
String = Bytes.decode("utf-8").replace("\r\n", "\n")
open("Output.txt", "w").write(String)
将复制您的原始文件。
我做了一个函数来清理一个列表
def cleanLists(self, lista):
lista = [x.strip() for x in lista]
lista = [x.replace('\n', '') for x in lista]
lista = [x.replace('\b', '') for x in lista]
lista = [x.encode('utf8') for x in lista]
lista = [x.decode('utf8') for x in lista]
return lista
对于Python 3来说,这是一种更安全和Pythonic的方法,可以将 "字节 "转换为 "字符串"。
def byte_to_str(bytes_or_str):
if isinstance(bytes_or_str, bytes): # Check if it's in bytes
print(bytes_or_str.decode('utf-8'))
else:
print("Object not of byte type")
byte_to_str(b'total 0\n-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file1\n-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file2\n')
产出:
total 0
-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file1
-rw-rw-r-- 1 thomas thomas 0 Mar 3 07:03 file2
从[sys - 系统特定参数和函数][1]。
要从标准流中写入或读取二进制数据,请使用底层的二进制缓冲区。
例如,要向stdout写入字节,使用sys.stdout.buffer.write(b'abc')
。
如果你想转换任何字节,而不仅仅是字符串转换为字节。
with open("bytesfile", "rb") as infile:
str = base64.b85encode(imageFile.read())
with open("bytesfile", "rb") as infile:
str2 = json.dumps(list(infile.read()))
然而,这并不是很有效率。 它将把一张2 MB的图片变成9 MB。
在Python 3.7上,你应该使用[subprocess.run
][1]并传递text=True
(以及capture_output=True
来捕获输出)。
command_result = subprocess.run(["ls", "-l"], capture_output=True, text=True)
command_result.stdout # is a `str` containing your program's stdout
text
曾经被称为universal_newlines
,在Python 3.7中被改变了 (嗯,别名)。
如果你想支持3.7之前的Python版本,可以用universal_newlines=True
代替text=True
。
[1]: https://docs.python.org/3/library/subprocess.html#subprocess.run