子进程调用在网络表单输入的unicode数据中失败 - 从其他地方输入相同数据正常

2 投票
2 回答
1355 浏览
提问于 2025-04-17 20:08

我有一个库,里面有一个函数,用来检查各种输入数据是否符合一些正则表达式,以确保数据是有效的。这个函数会被用来处理从网页表单通过CGI脚本接收到的输入(通过lighttpd),也会用来处理从sqlite数据库读取的输入(这些输入是通过gammu-smsd接收到的短信放进去的)。

输入有时候是英文,有时候是印地语,也就是用天城文书写的。无论如何,这些输入应该始终使用UTF-8编码。我在使用Python的re和regex模块时遇到了一些麻烦,这些模块在正确匹配天城文字符类时似乎有问题(你可以在这里看到一个例子——在那个情况下,使用regex代替re解决了问题,但之后我在regex上也遇到了麻烦)。命令行的'grep'看起来更可靠、更准确。因此,我决定使用子进程调用,将需要的字符串传递给grep,代码如下:

def invalidfield(datarecord,msgtype):
  for fieldname in datarecord:
    if (msgtype,fieldname) in mainconf["MSG_FORMAT"]:
        try:
            check = subprocess.check_output("echo '" + datarecord[fieldname] + "' | grep -E '" + mainconf["MSG_FORMAT"][msgtype,fieldname] + "'",shell=True)
        except subprocess.CalledProcessError:
            return fieldname
return None

现在,让我们用以下字符串作为输入来试试:न्याज अहमद्,并用以下正则表达式来检查它:^[[:alnum:] .]*[[:alnum:]][[:alnum:] .]*$

奇怪的是,完全相同的输入,当从数据库读取时,能够通过这个正则表达式(正如应该的那样),函数返回结果也正确。但当同样的字符串通过网页表单输入时,subprocess.check_out却失败,出现了这个错误:

File "/usr/lib/python2.7/subprocess.py", line 537, in check_output
  process = Popen(stdout=PIPE, *popenargs, **kwargs)
File "/usr/lib/python2.7/subprocess.py", line 679, in __init__
  errread, errwrite)
File "/usr/lib/python2.7/subprocess.py", line 1259, in _execute_child
  raise child_exception
TypeError: execv() arg 2 must contain only strings

我搞不清楚发生了什么。我已经修改了我的lighttpd.conf,使用了这个脚本,这应该至少确保lighttpd.conf使用的是utf-8字符集。我还使用了chardet模块,并对网页表单的输入运行了chardet.detect。我得到了这个结果:{'confidence': 1.0, 'encoding': 'ascii'}{'confidence': 0.99, 'encoding': 'utf-8'}

根据这个回答,我尝试将上面的datarecord[fieldname]替换为unicode(datarecord[fieldname]).encode('utf8'),还尝试先用'ascii'编码解码datarecord[fieldname]。后者失败了,出现了通常的'ordinal not in range'错误。

到底出了什么问题?我就是搞不明白!

2 个回答

0

我想补充一下Martijn Pieters的回答,他提到的解决方案在输入字符串为空的情况下会失败(和原来的函数不同,grep在匹配空字符串时会失败,即使正则表达式本身是允许的)。所以,要完整实现原来的函数,可以这样做:

if (msgtype,fieldname) in mainconf["MSG_FORMAT"]:
        if not datarecord[fieldname]:
            if not regex.search(mainconf["MSG_FORMAT"][msgtype,fieldname],datarecord[fieldname],regex.UNICODE):
                return fieldname
        else:               
            curenv = os.environ
            curenv['LC_ALL']="en_US.UTF-8"
            check = subprocess.Popen(['grep','-E', mainconf["MSG_FORMAT"][msgtype,fieldname]], stdin=subprocess.PIPE, env=curenv, stderr=subprocess.STDOUT,stdout=subprocess.PIPE)
            check.communicate (datarecord[fieldname]) 
            if check.returncode:
                return fieldname
return None

这样做是有效的,因为regex在空字符串上匹配得很好。

3

在这种情况下,你不想使用 echo;而是直接把你的输入写入 Popen() 对象的 stdin 管道中。

确保你的环境设置为正确的语言环境,这样 grep 才知道把输入当作 UTF-8 来解析:

env = dict(os.environ)
env['LC_ALL'] = 'en_US.UTF-8'
p = subprocess.Popen(['grep', '-E', mainconf["MSG_FORMAT"][msgtype,fieldname]], stdin=subprocess.PIPE, env=env)
p.communicate(datarecord[fieldname])
if p.returncode:
     return fieldname

撰写回答