windows powershell python-3.x unicode subprocess

windows - Decodifique la salida de PowerShell que posiblemente contenga caracteres Unicode no ASCII en una cadena de Python



python-3.x subprocess (2)

Necesito decodificar powerhell stdout llamado desde python en una cadena de python.

Mi objetivo final es obtener en forma de una lista de cadenas los nombres de los adaptadores de red en Windows. Mi función actual se ve así y funciona bien en Windows 10 con idioma inglés:

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

Otros usuarios tienen diferentes idiomas, por lo que value.decode(''ascii'') falla para algunos de ellos. Por ejemplo, un usuario informó que cambiar a decode(''ISO 8859-2'') funciona bien para él (por lo que no es UTF-8). ¿Cómo puedo saber la codificación para decodificar los bytes stdout devueltos por la llamada a powershell?

ACTUALIZAR

Después de algunos experimentos, estoy aún más confundido. La página de códigos en mi consola según lo devuelto por chcp es 437. Cambié el nombre del adaptador de red a un nombre que contenga caracteres no ascii y no cp437. En Get-NetAdapter | select Name | fl interactivo ejecutando Get-NetAdapter | select Name | fl Get-NetAdapter | select Name | fl Get-NetAdapter | select Name | fl muestra correctamente el nombre incluso su carácter no cp437. Cuando llamé a powershell desde python, los caracteres no ascii se convirtieron en caracteres ascii más cercanos (por ejemplo, ā a a, ž a z) y .decode(ascii) funcionó muy bien. ¿Podría este comportamiento (y la correspondiente solución) depender de la versión de Windows? Estoy en Windows 10, pero los usuarios podrían estar en Windows anterior hasta Windows 7.



La codificación de caracteres de salida puede depender de comandos específicos, por ejemplo:

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

Salida

cp437 b"/xff/xfe/x00/x00/x0c''/x00/x00/r/x00/x00/x00/n/x00/x00/x00" ''/u270c/r/n''

El carácter ✌ ( U+270C ) se recibe con éxito.

La codificación de caracteres del script secundario se establece utilizando PYTHONIOENCODING envvar dentro de la sesión de PowerShell. utf-32 para la codificación de salida para que sea diferente de las páginas de códigos ANSI y OEM de Windows para la demostración.

Observe que la codificación stdout del script de Python principal es la página de códigos OEM ( cp437 en este caso): el script se ejecuta desde la consola de Windows. Si redirige la salida de la secuencia de comandos Python principal a un archivo / tubería, la página de códigos ANSI (por ejemplo, cp1252 ) se usa de forma predeterminada en Python 3.

Para decodificar la salida de PowerShell que puede contener caracteres no codificables en la página de códigos OEM actual, puede configurar [Console]::OutputEncoding temporalmente (inspirado en los comentarios 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()))

Salida

cp437 ''/u270c/n'' ''/u270c/n'' ''/u270c/n''

Tanto fl como tee usan [Console]::OutputEncoding para stdout (el comportamiento predeterminado es como si | Write-Output se agregara a las tuberías). tee usa utf-16, para guardar un texto en un archivo. La salida muestra que ✌ ( U+270C ) se decodifica con éxito.

$OutputEncoding se usa para decodificar bytes en el medio de una tubería:

#!/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])

Salida

b''/xf0/x9f/x98/x8a/r/n''

eso es correcto: b''/xf0/x9f/x98/x8a''.decode(''utf-8'') == u''/U0001f60a'' . Con el valor predeterminado $OutputEncoding (ascii) obtendríamos b''????/r/n'' lugar.

Nota:

  • b''/n'' se reemplaza por b''/r/n'' pesar de usar API binaria como os.read/os.write ( msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) no tiene ningún efecto aquí)
  • b''/r/n'' se agrega si no hay nueva línea en la salida:

    #!/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()))

    Salida:

    piped: b''no newline in the input/r/n'' no pipe: b''no newline in the input''

    La nueva línea se agrega a la salida canalizada.

Si ignoramos los sustitutos solitarios, la configuración de UTF8Encoding permite pasar por canalizaciones todos los caracteres Unicode, incluidos los que no son BMP. El modo de texto podría usarse en Python si $env:PYTHONIOENCODING = "utf-8:ignore" está configurado.

En Get-NetAdapter | select Name | fl interactivo ejecutando Get-NetAdapter | select Name | fl Get-NetAdapter | select Name | fl Get-NetAdapter | select Name | fl muestra correctamente el nombre incluso su carácter no cp437.

Si stdout no se redirige, se utiliza la API Unicode para imprimir caracteres en la consola; cualquier carácter Unicode [BMP] se puede mostrar si la fuente de la consola (TrueType) lo admite.

Cuando llamé a powershell desde python, los caracteres no ascii se convirtieron en caracteres ascii más cercanos (por ejemplo, ā a a, ž a z) y .decode (ascii) funcionó muy bien.

Puede deberse a System.Text.InternalDecoderBestFitFallback establecido para [Console]::OutputEncoding : si un carácter Unicode no se puede codificar en una codificación determinada, entonces se pasa a la reserva (¿un mejor ajuste o ''?'' se usa en lugar del carácter original).

¿Podría este comportamiento (y la correspondiente solución) depender de la versión de Windows? Estoy en Windows 10, pero los usuarios podrían estar en Windows anterior hasta Windows 7.

Si ignoramos los errores en cp65001 y una lista de nuevas codificaciones compatibles con versiones posteriores, el comportamiento debería ser el mismo.