Я отвечаю на свой вопрос.
У меня до сих пор нет ответа на свой первый вопрос ( «Что вызывает такое странное поведение?» ), поэтому он все еще открыт , и мне это действительно любопытно.
Но я нашел обходной путь, чтобы получить правильные результаты, не решая исходную проблему.
Вот версия моей тестовой программы, которая дала то же самое правильный вывод при запуске из оболочки, а также из 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, и б) если да, то какой параметр должен быть установлен на какое значение.