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