我有一个python项目,我必须显示来自另一台具有未知编码的计算机的数据。我在Ubuntu VM上运行python 3。我得到的是一个字节流(可能是命令输出或cat'd文件或类似)。我只需尽可能地显示数据,无论来源如何。
作为测试,我正在尝试 cat / dev / urandom 并通过python以与我自己输入命令时相同的方式显示它。为了使其可重现,我使用了 head -n 2 / dev / urandom ,而不是使用cat得到的无穷无尽的流。
在bash中,当我cat,文件时,我得到标准随机gunk。我有LANG = en_US.ETF-8。很多角色都没有真正呈现(带有问号的菱形)或者只是空白(因为它显然不是UTF-8,它只是原始数据的随机字节)
eJ̘��}��jf��)���N�n��t��8=����X-�L�^t�M����Z���g�8#K T��c��z�ZO+�ϩD1{|EX
��)'���ei{W�r��畴��Ii�Y���
�}���+��;-�i-
S��Az
uV�1XBxFZ3+4��G�*��Q�+!
但是,如果我在python中读取文件并打印到标准输出,除非我使用'latin-1',否则会出现编码错误。我甚至尝试使用默认流中的编码,假设它继承自终端。这显然是不正确的,因为我需要管道远程端的流编码(我没有)。
>>> f = open("foo.txt", "rb")
>>> data = f.read(530)
>>> import sys
>>> sys.stdout.write(data.decode(sys.stdout.encoding))
正如所料,产生的错误是基于不遵循UTF8编码标准的随机数据:
>>> sys.stdout.write(data.decode(sys.stdout.encoding))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb1 in position 0: invalid start byte
所以,它从LANG(?)中获取UTF-8并应用它,但它只是有一个编码问题而且失败了。如果我明确地使用“latin-1”,我会得到类似的东西,但它与终端也不一样。
sys.stdout.write(data.decode('latin-1'))
收益率(概念示例 - 不是实际文本):
©7yIº*ø^Mÿ*Ig«áEIt±.Q ÈyT?æsÎ_%v1DÎú¹×,sÛÐûóÜun¢$&6YuApÁ¼pnòàJð
所以,问题是......如何阅读终端设置,以便我可以使用它们来解码和重现终端上会出现的内容?
我已经查看了其他一些问题:Why does Python print unicode characters when the default encoding is ASCII?和Convert bytes to a Python string并且它们涵盖了一些部分,但我不理解与shell / bash / etc的交互。
编辑(答案评估):
使用errors =“replace”让我接近。 os.write复制了cat的输出。差异似乎是基于错误字符如何聚集在一起,因此差异并不重要。
编辑编辑(实际解决方案):最后,我显然需要从原始机器读取语言环境/远程编码,以便我可以将其发送,然后将字节解释为该编码,然后转码为本地机器编码,然后显示。从这个意义上讲,项目没有经过深思熟虑,因为我没有收到远程信息,我显然需要它。我最终使用了这个(对于当前构建,因为它消除了错误):
sys.stdout.write(data.decode('utf-8', errors='replace'))
sys.stdout.flush()
编辑(清除问题文本以关注实际主题)
答案 0 :(得分:2)
解决问题,而不是问题(https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)
我有一个python项目,它使用python 3发送给各种Linux风格(vms,真机等)的各种用户。作为这个程序的一部分,python可能会向终端显示一个本地文件屏幕。我正在寻找一种方法来读取终端的设置以使用它们来显示数据。通过这种方式,用户可以设置他们的终端来处理扩展字符集或者其他任何东西,如果这是他们想要的。
您已经发现Python使用locale
来解析终端的可能编码 - 如果您使用str
或{{1}向stdout编写Python 3 write()
},你会发现它是为终端自动编码的。这意味着您的代码不需要(也不应该)检测其环境。
我说&#34;可能编码&#34;,因为字符的解码不是shell的责任,而是终端(终端/ iTerm / Putty等)的责任,它可能在用户上运行&# 39;桌面远程。您将不得不希望大多数人将print()
和终端编码保留为默认值,现在谢天谢地通常是UTF-8。
如果您要在本地计算机上打开文本文件,您还应该在读取该文件时应用编码(您可以直接使用解码/编码将其写出来但是您无法使用干净地操纵它。值得庆幸的是,locale
还将使用来自用户区域设置的open()
来设置解码文件的编码。
这意味着,如果用户locale.getpreferredencoding()
,则LANG='en_GB.UTF-8'
。如果您使用locale.getpreferredencoding() == "UTF-8"
打开文件,该文件将被解码为UTF-8到Python str。
您将能够将此数据写入/打印到控制台,相信您已经尽力解决我们必须使用的编码遗产。
答案 1 :(得分:1)
如果你想重现cat
会做什么,那么只需将数据写入stdout而不解码它:
import os
f = open("/dev/urandom", "rb")
data = f.read(1000)
os.write(1, data)
答案 2 :(得分:1)
sys.stdout
(以及sys.stdin
和sys.stderr
)是text files。它们具有与之关联的编码,您可以从它们恰当命名的encoding
属性中读取它们,并且它们希望使用字符串,它们将自动编码或解码,具体取决于I / O方向。在Linux上,您应该期望基于Python进程的初始环境的环境变量来选择编码。我不知道任何改变打开文件编码的机制,但在这种情况下,编码应该与终端期望的编码相同。
正如thatotherguy所写,你可以通过以二进制模式读取源文件,并使用低级cat
将字节发送到文件描述符来执行等效的os.write()
命令stdout
。但请注意,底层系统函数不一定总是写入指定的完整字节数,因此通常必须在循环中调用它以确保推出所有所需的字节。 Python docs没有指定,但由于该方法具有与底层系统调用相同的接口,因此最好假设它具有相同的语义。
但实际上,这一切都是以错误的方式进行的。如果您强制执行文件的字节的原始转储,那么您不会强迫用户配置其环境以支持扩展字符集,您强制他们配置其环境以支持 exact < / em>文件编码的字符集(或者是忍受乱码)。
更好的解决方案是将文件作为 text 文件打开,指定正确的编码(可能是您知道的),通过文件对象读取数据,以便Python对其进行解码正确地,并将结果字符串写入stdout
,从而为终端正确编码它们,至少因为这是可能的。这样,您可以在终端上容纳任何支持文件中所有字符的字符编码 - 它实际上不必与文件相同。
答案 3 :(得分:0)
你几乎就在那里,只需将errors='replace'
传递给解码功能。
with open('/dev/urandom', 'rb') as f:
x = f.read(100)
print(x.decode(sys.stdout.encoding, errors='replace'))
�{�ʛf$��s���<�w'`�i6�/��Z�ʫ;����ek|%�-+����V�U��;w>פ���TV��
�}���639
sys.stdout.write(x.decode(sys.stdout.encoding, errors='replace'))
�{�ʛf$��s���<�w'`�i6�/��Z�ʫ;����ek|%�-+����V�U��;w>פ���TV��
�}���639
os.write(1, x)
�{�ʛf$��s���<�w'`�i6�/���Z�ʫ;����ek|%�-+����V�U��;w>פ����TV��
�}���639