为什么Python子进程在CGI脚本中根据运行方式有不同的退出状态?
我正在尝试从Python运行下面的CGI脚本中的shell命令。有人能告诉我为什么当我在命令行中执行这个脚本时,exit_status
是0
,而当我从网页浏览器运行它时,exit_status
却是127
吗?非常感谢。另外,如果我尝试像echo something
这样的子进程(例如,exit_status = subprocess.call('echo something', shell=True)
),在终端中运行时退出状态会是零,但在浏览器中执行时却会出现500 Internal Server Error
。我真的很困惑……
#!/usr/bin/env python
import subprocess
print 'Content-type: text/html\n\n'
print '<html><head>'
print '</head><body>'
exit_status = subprocess.call('ls', shell=True)
print exit_status
print '</body></html>'
1 个回答
正如@abarnert在评论中提到的,使用 shell=True
一般来说是要避免的,除非你别无选择(比如你确实需要系统命令行的某些功能)。正如这个子进程文档的部分所解释的,使用命令行会引入一些严重的安全问题,尤其是当命令(包括参数)来自外部来源(比如表单提交)时。此外,你还得相信主机系统上的命令行配置是正确的,这对于一些特殊用户(比如用于网络服务的用户)来说并不总是安全的。除了这些安全隐患,单单为了执行一个可以直接执行的命令而额外启动一个命令行进程也是浪费资源。而且,依赖系统命令行可能会影响你的代码在不同平台上的兼容性,因为不同的平台对命令字符串会有不同的处理和规则。
在使用 subprocess
时,最好将命令作为参数列表传递,而不是一个单独的字符串,这在使用 shell=False
时尤其重要。虽然在所有平台上可能不是必须的,但在Linux上肯定是这样,这样可以让你的代码更具可移植性。此外,自己分割参数可以避免忘记转义或引用参数的问题。
另外,@abarbert正确地解释了,在CGI环境中,你可能不会正确设置 PATH
,所以最好通过绝对路径来引用所有内容。这也是为了在不同平台和环境中保持良好的可移植性(当然,硬编码二进制文件的路径会影响可移植性,但至少你可以最小化这个问题——谁知道你或其他人什么时候需要移植这段代码)。
考虑到这些,你的 ls
和 echo
示例应该像这样:
exit_status = subprocess.call(['/bin/ls'])
exit_status = subprocess.call(['/bin/echo', 'something'])
另外,许多命令行将 echo
视为内置命令,这会覆盖实际的 echo
命令——通过绝对路径引用(即 /bin/echo
)可以强制使用实际的命令,而不是内置的。
当作为CGI脚本运行时,内部服务器错误通常意味着你遇到了未捕获的Python异常。如果你查看你的网络服务器的日志文件(假设你有权限访问),你应该能找到异常的完整文本,但这在不同的服务器上可能会有所不同。当不使用 shell=True
时,我怀疑你会遇到 OSError
异常,因为找不到要运行的可执行文件。当你使用命令行时,你不会得到异常——你只会在标准错误输出中看到命令行的错误信息。这些信息应该也会在日志中重复。如果你确实遇到了异常,我怀疑是因为你运行的用户没有指定有效的登录命令——一些配置会将脚本作为使用类似 /bin/false
的用户运行,以避免账户被滥用。如果是这种情况,我预计返回代码会是 1
而不是 127
。
简而言之,查看一下你网络服务器的错误日志中的信息,最佳实践是避免使用 shell=True
,并确保将所有命令指定为参数列表,而不是用空格分隔的字符串。如果你能从日志中获取更多信息并在这里发布,我们可以更新我们的回答以提供更具体的帮助。
最后,如果可能的话,我会避免在网络服务器中使用任何调用 fork()
的代码,包括 subprocess
。有时候无法避免,但对于像 ls
这样简单的事情,你可以使用Python的函数来代替(os.listdir()
)。当你作为标准CGI脚本运行时,可能没问题,但如果你计划将代码移植到更高效的托管环境(例如mod-wsgi、fastcgi),那么在生成外部进程时必须非常小心,因为这会导致文件描述符和其他资源的重复使用。我并不是说这样做就不能安全,但这将来可能会成为混淆错误的潜在来源,所以如果可以的话,我真的建议避免这样做。更不用说为了处理网络请求而生成外部进程的相对低效,这在你计划扩展代码时可能会很重要。