[python scrips从shell或通过CGI运行时读取不同的unicode字符串

问题描述 投票:0回答:1

在我的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)必须更改什么,以便显示正确的代码点(776214)?


附录

我将这些行添加到程序中:

import sys

print(sys.getfilesystemencoding())

此行的输出是:

  • 从shell运行时:

    utf-8 
    

    正确。

  • 当从apache作为CGI脚本运行时:

    ascii  
    

    这是错误的。

所以,我的新问题是:

我如何告诉我的脚本,它总是应该使用utf-8作为文件系统编码?

python apache ubuntu unicode
1个回答
0
投票

我正在回答自己的问题。

我对我的第一个问题(“是什么原因导致这种奇怪的行为?”)仍然没有答案,所以这仍然是开放的,我对此很好奇。

但是我发现了一种解决方法,可以在不真正解决原始问题的情况下获得正确的结果。

这是我的测试程序的一个版本,当从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

但是这两种模式之间还有另一个区别,未记录:

  • 如果输入是字节序列,则python不在乎文件系统的编码。它始终将文件名读取为字节序列,并将这些序列附加到将作为输出的列表中。
  • 但是如果输入是其他内容(unicode字符串或文件描述符),那么它也在第一步中读取字节,但是随后使用当您调用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)如果有,则需要将哪个参数设置为哪个值。

© www.soinside.com 2019 - 2024. All rights reserved.