将可能包含非ASCII字符的poweshell输出解码为一个pythonstring

我需要解码从python调用powershell标准输出到一个pythonstring。

我的最终目标是获取一个string列表的forms在Windows中的networking适配器的名称。 我目前的function看起来像这样,并在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返回的标准输出字节?

UPDATE

经过一些实验,我更加困惑。 在我的控制台中,由chcp返回的代码页是437.我将networking适配器名称更改为包含非ascii和非cp437字符的名称。 在运行Get-NetAdapter | select Name | fl交互式Get-NetAdapter | select Name | fl Get-NetAdapter | select Name | fl Get-NetAdapter | select Name | fl正确地显示了这个名字,甚至是它的非cp437字符。 当我从python调用powershell时,非ascii字符被转换为最接近的ascii字符(例如,ā到a,ž到z)和.decode(ascii)很好地工作。 这种行为(和相应的解决scheme)可以依赖于Windows版本吗? 我在Windows 10上,但用户可能会在较旧的Windows到Windows 7。

输出字符编码可能取决于特定的命令,例如:

 #!/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的注释启发):

 #!/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' 

对于标准输出, fltee使用[Console]::OutputEncoding (默认行为就像将| 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'

注意:

  • 尽管使用二进制API,例如msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)在这里没有效果msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) b'\n'被替换为b'\r\n'
  • 如果输出中没有换行,则附加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允许通过管道传递包括非BMP字符的所有Unicode字符。 如果配置了$env:PYTHONIOENCODING = "utf-8:ignore"则可以在Python中使用文本模式。

在运行Get-NetAdapter | select Name | fl交互式Get-NetAdapter | select Name | fl Get-NetAdapter | select Name | fl 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字符不能在给定的编码中编码,那么它被传递给后备(或者是一个最合适的字符或'?'被用来代替原来的字符)。

这种行为(和相应的解决方案)可以依赖于Windows版本吗? 我在Windows 10上,但用户可能会在较旧的Windows到Windows 7。

如果我们忽略cp65001中的错误以及更高版本中支持的新编码列表,那么行为应该是相同的。

这是一个已经标记为wontfix的Python 2 bug: https ://bugs.python.org/issue19264

如果你想在Windows下工作,我必须使用Python 3。