Имена файлов Unicode в Windows с Python & subprocess.Popen()
Почему происходит следующее:
>>> u'\u0308'.encode('mbcs') #UMLAUT
'\xa8'
>>> u'\u041A'.encode('mbcs') #CYRILLIC CAPITAL LETTER KA
'?'
>>>
У меня есть приложение Python, принимающее имена файлов из операционной системы. Он работает для некоторых международных пользователей, но не для других.
Например, это имя файла Unicode:
и '\ u041a\u0433\u044b\u044b\u0448\u0444\u0442'
не будет кодироваться с кодировкой Windows "mbcs" (той, которая используется файловой системой, возвращается sys.getfilesystemencoding()). Я получаю "???????", показывая, что кодер не работает на этих символах. Но это не имеет никакого смысла, поскольку имя файла пришло от пользователя для начала.
Обновление: вот фон по моим причинам...
У меня есть файл в моей системе с именем на кириллице. Я хочу вызвать subprocess.Popen() с этим файлом в качестве аргумента. Popen не будет обрабатывать unicode. Обычно я могу уйти с кодировкой аргумента с кодеком, данным sys.getfilesystemencoding(). В этом случае он не будет работать
Ответы
Ответ 1
В Py3K - по крайней мере, из Python 3.2 - subprocess.Popen
и sys.argv
работают последовательно (по умолчанию unicode) строки в Windows. CreateProcessW
и GetCommandLineW
.
В Python - по крайней мере до v2.7.2 - subprocess.Popen
работает с аргументами Unicode. Он придерживается CreateProcessA
(while os.*
соответствует Unicode). И shlex.split
создает дополнительную бессмыслицу.
Pywin32 win32process.CreateProcess
также не автоматически переключается на версию W, и не существует win32process.CreateProcessW
. То же самое с GetCommandLine
.
Таким образом, нужно использовать ctypes.windll.kernel32.CreateProcessW...
.
Возможно, модуль подпроцесса должен быть исправлен по этой проблеме.
UTF8 на argv[1:]
с частными приложениями остается неуклюжим в Unicode OS. Такие трюки могут быть законными для 8-битных "Latin1" строковых ОС, таких как Linux.
UPDATE vaab создал исправленную версию Popen
для Python 2.7, которая устраняет проблему.
См. https://gist.github.com/vaab/2ad7051fc193167f15f85ef573e54eb9
Сообщение в блоге с пояснениями: http://vaab.blog.kal.fr/2017/03/16/fixing-windows-python-2-7-unicode-issue-with-subprocesss-popen/
Ответ 2
Документы для sys.getfilesystemencoding() говорят, что для Windows NT и более поздних версий имена файлов являются в основном Unicode. Если у вас есть допустимое имя файла в юникоде, зачем вам его кодировать с помощью mbcs?
Документы для модуля кодеков говорят, что mbcs кодирует с использованием "кодовой страницы ANSI" (которая будет отличаться в зависимости от локали пользователя), поэтому, если локаль не использует кириллические символы, splat.
Изменить: Таким образом, ваш процесс вызывает subprocess.Popen(). Если ваш вызываемый процесс находится под вашим контролем, два процесса могут быть согласны использовать UTF-8 в качестве формата Unicode Transport Format. В противном случае вам может потребоваться задать список рассылки pywin32. В любом случае отредактируйте свой вопрос, чтобы указать степень контроля над вызываемым процессом.
Ответ 3
Если вам нужно передать имя существующего файла, у вас может быть больше шансов на успех, передав версию файла Unicode 8.3.
У вас должен быть установлен pywin32 пакет, который вы можете сделать:
>>> import win32api
>>> win32api.GetShortPathName(u"C:\\Program Files")
'C:\\PROGRA~1'
Я считаю, что эти короткие имена файлов используют только символы ASCII, и поэтому вы должны иметь возможность использовать их в качестве аргументов в командной строке.
Если вам нужно указать также имена файлов, которые нужно создать, вы можете создать их с нулевым размером заранее из Python с использованием имен файлов Unicode и передать краткое имя файла в качестве аргумента.
ОБНОВЛЕНИЕ: пользователь bogdan правильно говорит о том, что генерация имени файла 8.3 может быть отключена (я также отключил ее, когда у меня был Windows XP на моем ноутбуке), поэтому вы не можете полагаться на них. Таким образом, в качестве еще более надуманного подхода при работе с томами NTFS можно жесткую ссылку имена файлов Unicode на простые ASCII; передайте имена файлов ASCII во внешнюю команду и затем удалите их.
Ответ 4
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я автор исправления, упомянутого ниже.
Чтобы поддерживать командную строку unicode в окнах с помощью python 2.7, вы можете использовать
этот патч до subprocess.Popen(..)
Ситуация
Поддержка Python 2 командной строки unicode в окнах очень плохая.
Серьезно прослушиваются:
-
выдача командной строки юникода в систему со стороны вызывающего абонента (через subprocess.Popen(..)
),
-
и чтение текущих аргументов unicode командной строки со стороны вызываемого абонента (через sys.argv
),
Подтверждено и не будет исправлено на Python 2. Они исправлены в Python 3.
Технические причины
В Python 2 реализация windows subprocess.Popen(..)
и sys.argv
использует системы с незашифрованными готовыми окнами CreateProcess(..)
(см. python code и MSDN doc CreateProcess) и не использует GetCommandLineW(..)
для sys.argv
.
В Python 3 реализация windows subprocess.Popen(..)
использует правильные системные вызовы Windows CreateProcessW(..)
, начиная с 3.0
(см. code in 3.0
) и sys.argv
использует GetCommandLineW(..)
, начиная с 3.3
(см. code в 3.3
).
Как это исправлено
Данный patch будет использовать модуль ctypes
для вызова окон C
системы CreateProcessW(..)
. Он предлагает новый фиксированный объект Popen
путем переопределения частного метода Popen._execute_child(..)
и частной функции _subprocess.CreateProcess(..)
для установки и использования CreateProcessW(..)
из системной библиотеки Windows таким образом, чтобы максимально имитировать, как это делается в Python 3.6
.
Как использовать его
Как использовать данный патч, демонстрируется с этим объяснением блога. Он также показывает, как читать текущие процессы
sys.argv
с другое исправление.