os.execl到底是做什么的?为什么我会得到这个错误?

9
我遇到了一些问题,需要在虚拟环境下使用passenger_wsgi模块部署Django。 passenger_wsgi.py文件中的Python代码应该能够解决我的问题:
import os, sys
INTERP = '/home/login/.virtualenvs/env_name/bin/python'
if sys.executable != INTERP:
    os.execl(INTERP, INTERP, *sys.argv)

我理解前三行,但第四行只有一个模糊的概念,而恰好这一行给了我一个错误:

/home/login/.virtualenvs/env_name/bin/python: can't find '__main__.py' in ''

那么os.execl在这里具体是做什么的?那个错误消息是什么意思?


你真的有一个名为 <login> 的目录吗?你是怎么做到的?那很难做到。 - S.Lott
不,我只是不想在这里发布我的实际路径 ;) - Monika Sulik
使用 <login><env_name> 使得尝试解决问题变得非常困难。我不知道你认为自己在保护什么“秘密”,但你应该选择没有 shell 特殊字符的名称。 - S.Lott
好的,我已经编辑了一个没有<和>的版本。 - Monika Sulik
3个回答

8

我并不想插手一个9年前的问题,只是在Google搜索“Python execl example”时偶然看到了这个帖子,差点被误导。因此,我发表帖子希望能够帮助其他访问者。

我同意https://stackoverflow.com/users/479633/mouad关于复现错误的方法,但不同意原因。错误发生的原因是当Python解释器以交互方式打开时,sys.argv将会是[''],因此一个空字符串会作为主脚本(目录)的路径传递给execl调用的Python解释器。由于在目录''(当前工作目录)中找不到主脚本文件__main__.py,所以会出现以下错误:

can't find '__main__.py' in ''

我无法理解https://stackoverflow.com/users/211075/monika-sulik是如何成功地将python脚本运行,同时将sys.argv的第一个成员设置为'',我猜测这段代码被复制并粘贴到了REPL中。
正如https://stackoverflow.com/users/845210/bjmcPython: os.execl() - what does it do exactly? Why am I getting this error?中提到的那样,文档是正确的,可以传递解释器路径两次,尽管第二次不需要。该函数的签名根源于UNIX的execve() API(https://linux.die.net/man/2/execve),它说:

argv是传递给新程序的参数字符串数组。按照惯例,这些字符串中的第一个应该包含与正在执行的文件相关联的文件名。

有一些程序利用了这种不一致性,例如busybox。
$ ln -s /bin/busybox cat
$ ./cat /etc/timezone
/UTC
$ python -c "import os; os.execl('./cat', 'cat', '/etc/timezone')"
/UTC
$ python -c "import os; os.execl('./cat', 'ls', '/etc/timezone')"
/etc/timezone

在UNIX类环境下,可执行路径和main()中的argv[0]之间的不一致性使得获取运行Python可执行文件的可靠路径非常困难(如果不是不可能的),这里有一个脚本来说明这一点:

import os
import sys


if len(sys.argv) >= 2 and sys.argv[1] == 'exec':
    os.execl('/usr/bin/python', 'ls', sys.argv[0])
else:
    print(sys.executable)
    print(sys.version)
    print(sys.argv)

运行这个脚本

$ python test.py exec
/bin/ls
2.7.13 (default, Nov 24 2017, 17:33:09)
[GCC 6.3.0 20170516]
['test.py']

根据文档(https://docs.python.org/3/library/sys.html#sys.executable),sys.executable的值为"/bin/ls",它是一个字符串,给出了Python解释器可执行二进制文件的绝对路径,在有意义的系统上。

在“有意义”的系统上,给出Python解释器可执行二进制文件的绝对路径的字符串。

关于sys.executable,如果Python开发人员无法弄清楚如何使其指向正在运行的Python可执行文件的路径,则在类UNIX环境中可能没有意义。如果有人告诉我不同的看法,我会非常感激。


6
也许你应该这样做:
os.execl(INTERP, *sys.argv) # don't pass again the interpreter path. 

我认为这份文档有误:http://wiki.dreamhost.com/Passenger_WSGI 关于exec:
Unix类操作系统的exec函数是一组函数,它们会将运行中的进程完全替换为传递给该函数的程序。
os.execl(path, arg0, arg1, ...)
os.execle(path, arg0, arg1, ..., env)
os.execlp(file, arg0, arg1, ...)
os.execlpe(file, arg0, arg1, ..., env)
os.execv(path, args)
os.execve(path, args, env)
os.execvp(file, args)
os.execvpe(file, args, env)

来源:http://docs.python.org/library/os.html

exec*()函数的“l”和“v”变体在传递命令行参数方面有所不同。如果代码编写时参数数量固定,那么“l”变体可能是最容易使用的;单个参数只需成为execl*()函数的附加参数即可。如果参数数量是可变的,并且将参数作为args参数传递到列表或元组中,则“v”变体很好。在任一情况下,子进程的参数应以要运行的命令的名称开头,但这并非强制执行。

编辑:

我刚刚在python shell中做了你正在做的事情,我得到了相同的错误:

>>> import os
>>> import sys
>>> os.execl('/home/login/projects/virtual/bin/python', '/home/login/projects/virtual/bin/python', *sys.argv)
/home/login/projects/virtual/bin/python: can't find '__main__.py' in ''

谢谢...我读了文档中的那一部分,但也没有完全理解。只传递INTERP参数一次可以消除错误,但不能解决问题。尽管它确实帮助我理解了正在发生的事情 ;-P 问题在于当只传递一次INTERP时,Python进程似乎已被替换,但是当我在新进程中打印sys.executable时,它仍然显示为'/usr/bin/python',这显然不是我想要的。 - Monika Sulik
是的,这就是我正在shell中调试它的方式...在error.log中没有出现错误(显然,由于错误日志记录很糟糕,因此调试passenger非常困难)。而且,当passenger_wsgi.py中出现错误时,django也不起作用,我也不会收到通过电子邮件发送的任何错误。 - Monika Sulik
好的...我已经点赞了你的答案,因为在尝试 os.execl(INTERP, *sys.argv) 方面是我目前最好的线索。有趣的是,当我这样做时,它似乎并没有实际改变 sys.executable 到我在 INTERP 中拥有的任何内容(我假设这就是重点?),但总是将其更改为 '/usr/bin/python'。所以即使 INTERP = '/usr/bin/python2.7' 而不是虚拟环境的路径,运行该命令也会使 sys.executable = '/usr/bin/python'。 - Monika Sulik
sys.executable 不会改变是正常的,你可以尝试在虚拟环境中运行 Python 解释器,你会发现 sys.executable == '/usr/bin/python',这是因为 sys.executable 并不会给你正在运行的解释器,而是给你系统默认的解释器。 - mouad
实际上不是这样的 - 当我通过在命令行中输入类似于/home/user/.virtualenv/env_name/bin/python2.7来运行解释器时,当我打印sys.executable时,它确切地显示了这个路径。 - Monika Sulik
1
我认为维基页面并没有错误。如果您在此处查看Python文档,它会说:“这些参数中的第一个将作为新程序的名称传递”,因此两次传递INTERP路径是正确的:一次用于调用解释器,另一次告诉它自己的位置。 - bjmc

-3
>>> import os
>>> help(os.execl)


execl(file, *args)
    execl(file, *args)

    Execute the executable file with argument list args, replacing the
    current process.

这可能有助于解决您的问题:http://ubuntuforums.org/showthread.php?t=1493979


谢谢,但我还是不明白...为什么我必须将INTERP的值放入文件名和其中一个参数中?"替换当前进程"是什么意思?错误消息呢,那是什么意思? - Monika Sulik
1
@Monika Sulik:替换当前进程意味着exec*命令不会杀死并创建新进程,它们只是替换从中调用命令的进程,进程ID不会改变,但是调用进程的数据、堆和栈将被新进程的数据、堆和栈所替换。 - mouad
@singularity - 谢谢,这确实帮助我搞清楚了进程的事情:) - Monika Sulik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接