在我的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脚本从Web服务器运行。我的网络服务器是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())
此行的输出是:
从shell运行时:
utf-8
正确。
当从apache作为CGI脚本运行时:
ascii
这是错误的。
所以,我的新问题是:
我如何告诉我的脚本,它总是应该使用utf-8
作为文件系统编码?
我正在回答自己的问题。
我对我的第一个问题(“是什么原因导致这种奇怪的行为?”)仍然没有答案,所以这仍然是开放的,我对此很好奇。
但是我发现了一种解决方法,可以在不真正解决原始问题的情况下获得正确的结果。
这是我的测试程序的一个版本,当从Shell以及从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字符串或文件描述符,则会生成Unicode字符串列表作为输出。但是,如果输入字节序列,则输出也将是字节序列的列表。这在此处有详细记录:https://docs.python.org/3/library/os.html#os.listdir
但是这两种模式之间还有另一个区别,未记录:
sys.getfilesystemencoding()
来解码此序列时将显示的编码个字节。如果字节序列包含不符合此编码的内容,则此“垃圾”将由代理字符代替。如果sys.getfilesystemencoding()
产生正确的输出,则效果很好。 ((更精确的说:如果python正确猜出了文件系统编码,这个方法就很好。sys.getfilesystemencoding()
不会做出这个猜想,它只会显示这个猜想的结果。)但是出于某种原因,我仍然很好奇大约,如果该脚本由Apache作为CGI脚本运行,则此猜测是错误的。在此处描述的设置中,实际文件系统编码为utf-8
,但python认为如果从Apache启动,则为ascii
。因此产生了错误的输出。解决方案是在不执行任何编码和转换的模式下使用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中的例程试图猜测文件系统编码,但存在错误。从Apache启动python时,它无法正常工作并做出错误的猜测。真正的解决方案是修复此错误。
[另一种可能性是,Apache 2的某些设置使Python相信可以在基于ascii的文件系统上工作。也许您只需要找到此设置并对其进行更正,但我不知道a)是否确实有这样的Apache设置,b)如果有,则需要将哪个参数设置为哪个值。