输出字符编码可能取决于特定的命令,例如:
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
环境变量设置子脚本的字符编码。我选择了utf-32
作为输出编码,以使其与Windows ANSI和OEM代码页有所不同,以进行演示。
请注意,父Python脚本的stdout编码是OEM代码页(在此示例中为cp437
)--该脚本是从Windows控制台运行的。如果将父Python脚本的输出重定向到文件/管道,则Python 3默认使用ANSI代码页(例如cp1252
)。
要解码可能包含当前OEM代码页无法解码的字符的powershell输出,您可以临时设置[Console]::OutputEncoding
(受@eryksun的评论启发):
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'
在stdout方面,fl
和tee
都使用[Console]::OutputEncoding
(默认行为就好像在管道末尾添加了| Write-Output
)。tee
使用utf-16来保存文本到文件中。输出结果显示✌(U+270C)已成功解码。
$OutputEncoding
用于在管道中解码字节:
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'
is replaced with b'\r\n'
despite using binary API such as os.read/os.write
(msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
has no effect here)
b'\r\n'
is appended if there is no newline in the output:
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))"'''
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()))
Output:
piped: b'no newline in the input\r\n'
no pipe: b'no newline in the input'
The newline is appended to the piped output.
如果忽略孤立的代理,那么设置
UTF8Encoding
可以通过管道传递所有Unicode字符,包括非BMP字符。如果配置了
$env:PYTHONIOENCODING = "utf-8:ignore"
,则可以在Python中使用文本模式。
在交互式powershell中运行
Get-NetAdapter | select Name | fl
将正确地显示名称,即使是非cp437字符。
如果未重定向标准输出,则使用Unicode API将字符打印到控制台 - 如果控制台(TrueType)字体支持它,则可以显示任何[BMP] Unicode字符。
当我从python调用powershell时,非ASCII字符被转换为最接近的ASCII字符(例如ā变成a,ž变成z),.decode(ascii)工作得很好。这可能是由于
System.Text.InternalDecoderBestFitFallback
设置为
[Console]::OutputEncoding
而导致的 - 如果一个Unicode字符不能编码成给定的编码,则被传递给回退(使用一个最佳匹配字符或
'?'
代替原始字符)。
如果我们忽略cp65001中的错误和一系列在后续版本中支持的新编码,则行为应该是相同的。是否与Windows版本有关呢?我在Windows 10上运行,但用户可能在Windows 7及以下版本上运行。
powershell
输出作为 Unicode 文本获取,则应将其放入标题中(我不知道“默认 Windows 显示语言编码”应该是什么)。检查 powershell 是否接受显式参数来指定其 stdout 编码($OutputEncoding
)。无关:在 Windows 上使用字符串传递命令,即使用'a | b | c'
而不是['a', '|', 'b', '|', 'c']
。 - jfsuniversal_newlines=True
启用文本模式(是的,拼写不太直观)。(2) cp437 和 cp1252 的编码都与 ASCII 编码兼容,适用于 ASCII 字符(如果执行.decode('ascii', 'strict')
操作成功,则意味着 stdout 中的所有字节都在 ASCII 范围内。它无法区分 cp437 和 cp1252)。 - jfsprint(check_output(['powershell', 'echo É']))
会得到什么结果?(我不确定如何在 PowerShell 中写'echo É'
)。如果输出中看到b'\x90'
,则编码为 cp437。如果看到b'\xc9'
,则编码为cp1252。另外,如果您不想调用.decode('utf-8')
,可以使用for line in io.TextIOWrapper(process.stdout, encoding='utf-8'):
。 - jfsctypes.windll.kernel32.SetConsoleOutputCP(1252);``p = subprocess.Popen('powershell echo $([char]0xc9)', stdout=subprocess.PIPE);``p.stdout.read()
。奇怪的是,如果我传递creationflags=DETACHED_PROCESS
,这样powershell.exe就不会附加到控制台,它甚至没有一个合理的ANSI代码页默认值。它根本没有输出任何东西。 - Eryk Sun