python scrips читает разные строки Unicode при запуске из оболочки или через CGI - PullRequest
0 голосов
/ 29 мая 2020

На моем сервере Ubuntu у меня есть каталог, содержащий эти два файла:

testDir# ls -als
insgesamt 12
4 drwxr-xr-x 2 root root 4096 Mai 29 15:12 .
4 drwxr-xr-x 6 root root 4096 Mai 28 18:38 ..
0 -rw-r--r-- 1 root root    0 Mai 28 19:17 Ö.txt
4 -rw-r--r-- 1 root root    9 Mai 28 19:16 Ö.txt

Имена файлов выглядят одинаково, но это не так. Файл с размером 0 имеет 1 символ перед точкой (кодовая точка Unicode 214 = Ö), другой файл (размер = 9) имеет два символа (кодовая точка 79 = O, за которой следует 776 = ¨, которая является объединяющим символом и изменяет символ перед ним). Чтобы отобразить кодовые точки Unicode, я написал этот небольшой сценарий:

#!/usr/bin/env python3

import os

def printFileList(fileList):
    for file in fileList:
        string = ""
        for char in file:
            string += str(ord(char)) + " "
        string += "<br>"
        print(string)

print("Content-Type: text/html\n")

printFileList(os.listdir("testDir"))

printFileList(["Ö.txt", "Ö.txt"])

Как видите, я сначала прочитал имена файлов из операционной системы и отобразил кодовые точки символов имен файлов. Затем я делаю то же самое, но со строками, которые жестко записаны в программном коде.

Когда я запускаю эту программу из оболочки, я получаю следующий результат:

testDir# ./test.py
Content-Type: text/html

79 776 46 116 120 116 <br>
214 46 116 120 116 <br>
79 776 46 116 120 116 <br>
214 46 116 120 116 <br>

Но этот скрипт (чтобы быть более точным: более продвинутая версия этого сценария) предназначена для запуска как сценарий CGI с веб-сервера. Мой веб-сервер - Apache 2, и когда я вызываю этот скрипт из браузера, я получаю следующий результат:

79 56524 56456 46 116 120 116 
56515 56470 46 116 120 116 
79 776 46 116 120 116 
214 46 116 120 116

Строка Content-Type: text/html является частью протокола http и не будет отображаться, и <br> отображается как разрывы строк, поэтому эти части не отображаются в браузере по уважительным причинам. Но посмотрите на числа!

То, что должно быть 776, это 56524 56456 в первой строке, а во второй строке 214 превратилось в 56515 56470. Но это произошло только с именами файлов, прочитанными из операционной системы. Жестко закодированные строки верны.

Мои вопросы:

1) Что вызывает такое странное поведение?
2) Что нужно изменить, чтобы правильные кодовые точки (776 и 214) показаны?


добавление

Я добавил эти строки в свою программу:

import sys

print(sys.getfilesystemencoding())

Результат этой строки:

  • при запуске из оболочки:

    utf-8 
    

    , что правильно.

  • при запуске из apache как CGI -script:

    ascii  
    

    что неверно.

Итак, мой новый вопрос:

Как я могу сказать своему скрипту, что он всегда следует использовать utf-8 в качестве кодировки файловой системы?

1 Ответ

0 голосов
/ 30 мая 2020

Я отвечаю на свой вопрос.

У меня до сих пор нет ответа на свой первый вопрос ( «Что вызывает такое странное поведение?» ), поэтому он все еще открыт , и мне это действительно любопытно.

Но я нашел обходной путь, чтобы получить правильные результаты, не решая исходную проблему.

Вот версия моей тестовой программы, которая дала то же самое правильный вывод при запуске из оболочки, а также из Apache в качестве сценария CGI:

#!/usr/bin/env python3

import os

def printFileList(fileList):
    for file in fileList:
        file = file.decode("utf-8")
        string = ""
        for char in file:
            string += str(ord(char)) + " "
        string += "<br>"
        print(string)

print("Content-Type: text/html\n")

printFileList(os.listdir("testDir".encode("utf-8")))

printFileList(["Ö.txt".encode("utf-8"), "Ö.txt".encode("utf-8")])

И вот почему это работает:

os.listdir производит список строк Unicode в качестве вывода если это входная строка в Юникоде или файловый дескриптор. Но если вы введете последовательность байтов, на выходе также будет список последовательностей байтов. Это хорошо документировано здесь: https://docs.python.org/3/library/os.html#os .listdir

Но есть еще одно различие между этими двумя режимами, которое не задокументировано:

  • Если ввод представляет собой последовательность байтов, python не заботится о кодировке файловой системы. Он всегда считывает имена файлов как последовательности байтов и добавляет эти последовательности в список, который будет выводиться.
  • Но если ввод - это что-то еще (строка Unicode или дескриптор файла), тогда он также на первом шаге читает байты, но затем использует кодировку, которая будет отображаться, когда вы вызываете sys.getfilesystemencoding() для декодировать эту последовательность байтов. Если последовательность байтов содержит что-то, что не соответствует этой кодировке, этот «мусор» будет заменен суррогатными символами.
    Это хорошо работает, если sys.getfilesystemencoding() дает правильный результат. (Точнее: это хорошо работает, если python правильно угадал кодировку файловой системы. sys.getfilesystemencoding() не делает этого предположения, он только отображает результат этого предположения.) Но по какой-то причине я Мне все еще интересно, это предположение неверно, если сценарий запускается Apache как сценарий CGI. В описанной здесь настройке реальная кодировка файловой системы - utf-8, но python считает, что это было ascii, если она была запущена с Apache. И поэтому он выдал неправильный вывод.

Решение состоит в том, чтобы использовать os.listdir в том режиме, в котором он не выполняет никаких кодировок и преобразований. А это значит: байты на входе, байты на выходе.

Для этого нужно заменить

os.listdir("testDir")

на

os.listdir("testDir".encode("utf-8"))

Теперь os.listdir будет работать в байтовый режим, и его вывод также будет списком последовательностей байтов. Чтобы использовать их как строки Unicode, вам просто нужно декодировать последовательности байтов с помощью этой строки:

file = file.decode("utf-8")

(кодировка в последней строке моей маленькой программы ("Ö.txt".encode("utf-8")) было необходимо только потому, что моя функция printFileList теперь больше не могла обрабатывать списки строк Unicode, а только списки последовательностей байтов.)


Но будьте осторожны: это не решение проблемы. Это только обходной путь. Если вы реализуете его, как описано здесь, он будет работать только в том случае, если фактическая кодировка файловой системы действительно равна utf-8.

Я думаю, что подпрограмма в python, которая пытается угадать кодировку файловой системы, имеет Жук. Он не работает должным образом и делает неправильное предположение, когда python запускается с Apache. Реальным решением было бы исправить эту ошибку.

Другая возможность состоит в том, что существует некоторая неправильная установка Apache 2, которая заставляет Python полагать, что он работает в файловой системе на основе ascii. Возможно, вам просто нужно найти эту настройку и исправить ее, но я понятия не имею, а) существует ли действительно такая настройка Apache, и б) если да, то какой параметр должен быть установлен на какое значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...