Décoder la sortie powershell contenant peut-être des caractères unicode non ascii dans une chaîne python
je dois décoder powershell stdout appelé de python dans une chaîne de Python.
Mon but ultime est d'obtenir dans une forme d'une liste de chaînes de caractères les noms des cartes réseau dans Windows. Ma fonction actuelle ressemble à cela et fonctionne bien sur Windows 10 avec la langue anglaise:
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'rn'):
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
, d'Autres utilisateurs ont des langues différentes, donc value.decode('ascii')
échoue pour certains d'entre eux. Par exemple: un utilisateur a déclaré que le passage à decode('ISO 8859-2')
fonctionne bien pour lui (donc il n'est pas de l'UTF-8). Comment puis-je connaître l'encodage pour décoder les octets stdout retournés par call à powershell?
UPDATE
après quelques expériences, je suis encore plus confus. Codepage dans ma console comme retourné par chcp
est 437. J'ai changé le nom de l'adaptateur réseau en un nom contenant des caractères non-ascii et non-cp437. Dans interactive powershell, l'exécution Get-NetAdapter | select Name | fl
affichait correctement le nom, même son caractère non-cp437. Quand j'ai appelé powershell à partir de Python, les caractères non-ascii ont été convertis en caractères ASCII les plus proches (par exemple, ❚ à a, ž à z) et .decode(ascii)
a bien fonctionné. Ce comportement (et la solution correspondante) pourrait-il dépendre de la version de Windows? Je suis sur Windows 10, mais les utilisateurs pourraient être sur les anciens Windows vers Windows 7.
2 réponses
le codage du caractère de sortie peut dépendre de commandes spécifiques, par exemple:
#!/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)))
sortie
cp437
b"\xff\xfe\x00\x00\x0c'\x00\x00\r\x00\x00\x00\n\x00\x00\x00"
'\u270c\r\n'
Le caractère ( U+270C ) est reçu avec succès.
le codage des caractères du script enfant est défini en utilisant PYTHONIOENCODING
envvar à l'intérieur de la session PowerShell. J'ai choisi utf-32
pour le codage de sortie de sorte qu'il serait différent de Windows ANSI et OEM pages de code pour la démonstration.
notez que l'encodage stdout du script parent Python est la page de code OEM ( cp437
dans ce cas) -- le script est exécuté depuis la console Windows. Si vous redirigez la sortie du script parent Python vers un fichier/pipe, alors la page de code ANSI (par exemple, cp1252
) est utilisée par défaut dans Python 3.
pour décoder la sortie powershell qui pourrait contenir des caractères indécodables dans la page de code OEM actuelle, vous pourrait mettre [Console]::OutputEncoding
Temporairement (inspiré par les commentaires de @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()))
sortie
cp437
'\u270c\n'
'\u270c\n'
'\u270c\n'
à la fois fl
et tee
utiliser [Console]::OutputEncoding
pour stdout (le comportement par défaut est comme si | Write-Output
est ajouté aux pipelines). tee
utilise utf-16, pour enregistrer un texte dans un fichier. La sortie montre que la valeur de l'option ( U+270C ) est décodée avec succès.
$OutputEncoding
est utilisé pour décoder des octets dans le milieu d'un pipeline:
#!/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])
sortie
b'\xf0\x9f\x98\x8a\r\n'
qui est correcte: b'\xf0\x9f\x98\x8a'.decode('utf-8') == u'\U0001f60a'
. Avec le défaut $OutputEncoding
(ascii) nous obtiendrions b'????\r\n'
à la place.
Note:
-
b'\n'
est remplacé parb'\r\n'
malgré l'utilisation D'API binaires tels queos.read/os.write
(msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
n'a aucun effet ici) -
b'\r\n'
est ajouté s'il n'y a pas de nouvelle ligne dans la sortie:#!/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()))
sortie:
piped: b'no newline in the input\r\n' no pipe: b'no newline in the input'
la nouvelle ligne est ajoutée à la sortie pipée.
si nous ignorons les substituts solitaires, le paramètre UTF8Encoding
permet de passer par des pipes tous les caractères Unicode incluant les caractères non-BMP. Le mode texte pourrait être utilisé en Python Si $env:PYTHONIOENCODING = "utf-8:ignore"
est configurer.
dans l'exécution interactive powershell
Get-NetAdapter | select Name | fl
affichait correctement le nom, même son caractère non-cp437.
si stdout n'est pas redirigé, alors L'API Unicode est utilisée, pour imprimer des caractères à la console -- n'importe quel caractère Unicode [BMP] peut être affiché si la police de la console (TrueType) le supporte.
quand j'ai appelé powershell des caractères non-ascii de python ont été convertis en caractères ascii les plus proches (par ex.decode (ascii) a bien fonctionné.
cela pourrait être dû à System.Text.InternalDecoderBestFitFallback
défini pour [Console]::OutputEncoding
-- si un caractère Unicode ne peut pas être encodé dans un encodage donné, alors il est passé à la repli (soit un caractère mieux adapté ou '?'
est utilisé à la place du caractère original).
ce comportement (et la solution correspondante) pourrait-il être Windows dépendant de la version? Je suis sur Windows 10, mais les utilisateurs pourraient être sur les anciens Windows vers Windows 7.
si nous ignorons les bogues dans cp65001 et une liste de nouveaux encodages qui sont supportés dans les versions ultérieures, alors le comportement devrait être le même.
c'est un bug Python 2 déjà marqué wontfix: https://bugs.python.org/issue19264
je dois utiliser Python 3 si vous voulez le faire fonctionner sous Windows.