Ответ 1
Кодировка выходного символа может зависеть от конкретных команд, например:
#!/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) символ получен успешно.
Кодировка символов дочернего сценария устанавливается с помощью envvar PYTHONIOENCODING
внутри сеанса PowerShell. Я выбрал utf-32
для выходной кодировки, чтобы она отличалась от кодовых страниц Windows ANSI и OEM для демонстрации.
Обратите внимание, что стандартная кодировка родительского сценария Python - это кодовая страница OEM (в данном случае cp437
) - сценарий запускается из консоли Windows. Если вы перенаправите вывод родительского сценария Python в файл/канал, то в Python 3 по умолчанию будет использоваться кодовая страница ANSI (например, cp1252
).
Чтобы декодировать вывод powershell, который может содержать символы, которые невозможно декодировать в текущей кодовой странице OEM, вы можете временно установить [Console]::OutputEncoding
(вдохновлено @eryksun 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
для стандартного вывода (поведение по умолчанию такое, как будто | 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'
, несмотря на использование двоичного API, такого какos.read/os.write
(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
позволяет передавать по каналам все символы Юникода, включая символы не-BMP. Текстовый режим может использоваться в Python, если настроен $env:PYTHONIOENCODING = "utf-8:ignore"
.
В интерактивном режиме PowerShell
Get-NetAdapter | select Name | fl
правильно отображалось имя, даже не символ cp437.
Если стандартный вывод не перенаправлен, то для печати символов в консоль используется API-интерфейс Unicode - любой символ [BMP] Unicode может отображаться, если его поддерживает шрифт консоли (TrueType).
Когда я вызывал powershell из python, символы не-ascii были преобразованы в наиболее близкие символы ascii (например, от a до a, от z до z), и .decode(ascii) работал хорошо.
Это может быть связано с тем, что System.Text.InternalDecoderBestFitFallback
установлено для [Console]::OutputEncoding
- если символ Unicode не может быть закодирован в заданной кодировке, то он передается в качестве запасного варианта (вместо знака используется либо наиболее подходящий символ, либо '?'
). оригинальный персонаж).
Может ли это поведение (и, соответственно, решение) зависеть от версии Windows? Я нахожусь на Windows 10, но пользователи могли быть на более старой Windows до Windows 7.
Если мы игнорируем ошибки в cp65001 и список новых кодировок, которые поддерживаются в более поздних версиях, то поведение должно быть таким же.