我需要将从python调用的powershell stdout解码为python字符串。
我的最终目标是以字符串列表的形式获取Windows中网络适配器的名称。我当前的功能看起来像这样,并且在带有英语的Windows 10上运行良好:
def get_interfaces():
ps = subprocess.Popen(['powershell', 'Get-NetAdapter', '|', 'select Name', '|', 'fl'], stdout = subprocess.PIPE)
stdout, stdin = ps.communicate(timeout = 10)
interfaces = []
for i in stdout.split(b'\r\n'):
if not i.strip():
continue
if i.find(b':')<0:
continue
name, value = [ j.strip() for j in i.split(b':') ]
if name == b'Name':
interfaces.append(value.decode('ascii')) # This fails for other users
return interfaces
其他用户使用不同的语言,因此value.decode('ascii')
对其中一些语言失败。例如。一位用户报告说,更改为decode('ISO 8859-2')
对他来说效果很好(因此它不是UTF-8)。我怎么知道编码来解码通过调用powershell返回的stdout字节?
更新
经过一些实验后,我更加困惑。我chcp
返回的控制台中的代码页是437.我将网络适配器名称更改为包含非ascii和非cp437字符的名称。在交互式PowerShell中,运行Get-NetAdapter | select Name | fl
正确显示名称甚至是非cp437字符。当我从python中调用powershell时,非ascii字符被转换为最接近的ascii字符(例如ā到a,ž到z),而.decode(ascii)
工作得很好。这种行为(以及相应的解决方案)可能依赖于Windows版本吗?我在Windows 10上,但用户可能在Windows 7以下的Windows上。
答案 0 :(得分:3)
输出字符编码可能取决于特定命令,例如:
#!/usr/bin/env python3
import subprocess
import sys
encoding = 'utf-32'
cmd = r'''$env:PYTHONIOENCODING = "%s"; py -3 -c "print('\u270c')"''' % encoding
data = subprocess.check_output(["powershell", "-C", cmd])
print(sys.stdout.encoding)
print(data)
print(ascii(data.decode(encoding)))
cp437
b"\xff\xfe\x00\x00\x0c'\x00\x00\r\x00\x00\x00\n\x00\x00\x00"
'\u270c\r\n'
✌(U+270C)字符已成功收到。
子脚本的字符编码是使用PowerShell会话中的PYTHONIOENCODING
envvar设置的。我已选择utf-32
作为输出编码,以便它与演示的Windows ANSI和OEM代码页不同。
请注意,父Python脚本的stdout编码是OEM代码页(在本例中为cp437
) - 脚本从Windows控制台运行。如果将父Python脚本的输出重定向到文件/管道,则Python 3中默认使用ANSI代码页(例如cp1252
)。
要解码可能包含当前OEM代码页中不可解码字符的powershell输出,您可以暂时设置[Console]::OutputEncoding
(受@eryksun's comments启发):
#!/usr/bin/env python3
import io
import sys
from subprocess import Popen, PIPE
char = ord('✌')
filename = 'U+{char:04x}.txt'.format(**vars())
with Popen(["powershell", "-C", '''
$old = [Console]::OutputEncoding
[Console]::OutputEncoding = [Text.Encoding]::UTF8
echo $([char]0x{char:04x}) | fl
echo $([char]0x{char:04x}) | tee {filename}
[Console]::OutputEncoding = $old'''.format(**vars())],
stdout=PIPE) as process:
print(sys.stdout.encoding)
for line in io.TextIOWrapper(process.stdout, encoding='utf-8-sig'):
print(ascii(line))
print(ascii(open(filename, encoding='utf-16').read()))
cp437
'\u270c\n'
'\u270c\n'
'\u270c\n'
fl
和tee
都将[Console]::OutputEncoding
用于stdout(默认行为就像| Write-Output
附加到管道一样)。 tee
使用utf-16将文本保存到文件中。输出显示✌(U+270C)已成功解码。
$OutputEncoding
用于解码管道中间的字节:
#!/usr/bin/env python3
import subprocess
cmd = r'''
$OutputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding
py -3 -c "import os; os.write(1, '\U0001f60a'.encode('utf-8')+b'\n')" |
py -3 -c "import os; print(os.read(0, 512))"
'''
subprocess.check_call(["powershell", "-C", cmd])
b'\xf0\x9f\x98\x8a\r\n'
这是正确的:b'\xf0\x9f\x98\x8a'.decode('utf-8') == u'\U0001f60a'
。使用默认的$OutputEncoding
(ascii),我们会得到b'????\r\n'
。
注意:
b'\n'
已替换为b'\r\n'
,尽管使用os.read/os.write
等二进制API(msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
此处无效) b'\r\n'
:
#!/usr/bin/env python3
from subprocess import check_output
cmd = '''py -3 -c "print('no newline in the input', end='')"'''
cat = '''py -3 -c "import os; os.write(1, os.read(0, 512))"''' # pass as is
piped = check_output(['powershell', '-C', '{cmd} | {cat}'.format(**vars())])
no_pipe = check_output(['powershell', '-C', '{cmd}'.format(**vars())])
print('piped: {piped}\nno pipe: {no_pipe}'.format(**vars()))
输出:
piped: b'no newline in the input\r\n'
no pipe: b'no newline in the input'
换行符附加到管道输出。
如果我们忽略单独的代理,那么设置UTF8Encoding
允许通过管道传递所有Unicode字符,包括非BMP字符。如果配置了$env:PYTHONIOENCODING = "utf-8:ignore"
,则可以在Python中使用文本模式。
在交互式PowerShell中,运行
Get-NetAdapter | select Name | fl
正确显示名称甚至是非cp437字符。
如果未重定向stdout,则使用Unicode API将字符打印到控制台 - 如果控制台(TrueType)字体支持,则可以显示任何[BMP] Unicode字符。
当我从python中调用powershell时,非ascii字符转换为最接近的ascii字符(例如ā到a,ž到z)和.decode(ascii)工作得很好。
可能是由于System.Text.InternalDecoderBestFitFallback
设置为[Console]::OutputEncoding
- 如果Unicode字符无法在给定编码中编码,则会将其传递给回退(最适合的char或{使用{1}}代替原始字符。
此行为(以及相应的解决方案)可能依赖于Windows版本吗?我在Windows 10上,但用户可能在Windows 7以下的Windows上。
如果我们忽略cp65001中的错误以及更高版本支持的新编码列表,那么行为应该是相同的。
答案 1 :(得分:-1)
已经标记为wontfix的Python 2错误:https://bugs.python.org/issue19264
如果你想让它在Windows下工作,我必须使用Python 3。