相对路径(包含斜杠的路径)无论你做什么,都不会在任何
PATH
中进行检查。它们仅相对于当前工作目录。如果您需要解析相对路径,则必须手动搜索
PATH
。
如果您想要运行一个程序,相对于Python脚本的位置,请使用
__file__
,从那里找到程序的绝对路径,然后在
Popen
中使用绝对路径。
在当前进程的环境变量
PATH
中搜索
关于Python如何处理裸命令(没有斜杠)有
一个Python bug跟踪器问题。基本上,在Unix / Mac上,当参数
env = None
时,
Popen
的行为类似于
os.execvp
(在末尾观察并记录了一些意外的行为):
引用:
在POSIX上,该类使用
os.execvp()
类似的行为来执行子程序。
这实际上适用于
shell = False
和
shell = True
,只要
env = None
。此行为的含义在函数
os.execvp
的文档中有解释:
这些变体(
execlp()
,
execlpe()
,
execvp()
, 和
execvpe()
)中,如果文件名以“p”结尾,则使用
PATH
环境变量来定位程序文件。当环境被替换时(使用下一段讨论的
exec*e
变体之一),新环境将作为
PATH
变量的来源。
对于
execle()
,
execlpe()
,
execve()
, 和
execvpe()
(注意它们都以“e”结尾),
env参数必须是一个映射,用于定义新进程的环境变量(这些变量代替当前进程的环境变量);而
execl()
,
execlp()
,
execv()
, 和
execvp()
函数会导致新进程继承当前进程的环境。
第二段引用的意思是,
execvp
将使用当前进程的环境变量。结合第一段引用,我们可以推断出
execvp
将从当前进程的环境中使用环境变量
PATH
的值。这意味着
Popen
会查看
Python启动时(运行
Popen
实例的Python)
PATH
的值,并且无论更改多少次
os.environ
都不能解决这个问题。
此外,在Windows上,如果
shell=False
,
Popen
根本不关心
PATH
,并且只会在相对于当前工作目录中查找。
shell=True
的作用
如果我们将
shell=True
传递给
Popen
会发生什么?在这种情况下,
Popen
只需调用shell:
shell参数(默认为False
)指定是否使用shell作为要执行的程序。
That is to say, Popen
does the equivalent of:
Popen(['/bin/sh', '-c', args[0], args[1], ...])
换句话说,使用
shell=True
,Python将直接执行
/bin/sh
,而不进行任何搜索(通过将参数
executable
传递给
Popen
可以更改此设置,如果它是没有斜杠的字符串,则Python将其解释为要在当前进程环境的
PATH
值中搜索的shell程序名称,即在
shell=False
的情况下搜索程序的方式)。
反过来,
/bin/sh
(或我们的shell
executable
)将在其自己的环境的
PATH
中查找要运行的程序,这与Python(当前进程)的
PATH
相同,根据上面“也就是说...”后面的代码推断出来(因为该调用具有
shell=False
,所以已经在前面讨论过了)。 因此,只要
env=None
,我们就会获得类似于
execvp
的行为,无论是
shell=True
还是
shell=False
。
向Popen
传递env
那么,如果我们将
env=dict(PATH=...)
传递给
Popen
(从而在由
Popen
运行的程序的环境中定义了一个环境变量
PATH
),会发生什么?
在这种情况下,新环境用于搜索要执行的程序。引用
Popen
的文档:
如果
env不是
None
,则必须是定义新进程环境变量的映射;这些变量代替了继承当前进程环境的默认行为。
结合上述观察和使用
Popen
进行实验,这意味着在这种情况下,
Popen
的行为类似于函数
os.execvpe
。如果
shell=False
,Python会在新定义的
PATH
中查找给定程序。如上所述,对于
shell=True
,在这种情况下,程序要么是
/bin/sh
,要么是使用参数
executable
给定的程序名,然后在新定义的
PATH
中搜索此替代(shell)程序。
此外,如果
shell=True
,则
在shell内部,shell将用于查找
args
中给定的程序的搜索路径是通过
env
传递给
Popen
的
PATH
的值。
因此,当
env != None
时,
Popen
在
env
的
PATH
键的值中搜索(如果
env
中存在
PATH
键)。
传播除
PATH
以外的环境变量作为参数。
关于环境变量除了PATH
之外还有一个警告:如果命令中需要这些变量的值(例如作为运行的程序的命令行参数),即使这些变量在传递给Popen
的env
中存在,如果没有使用shell=True
,它们也不会被解释。
可以很容易地避免这种情况而无需更改shell=True
:直接将这些值插入到传递给Popen
的args
列表参数中。(此外,如果这些值来自Python自己的环境,则可以使用os.environ.get
方法获取它们的值)。
使用/usr/bin/env
如果你只需要路径评估而不想通过shell运行你的命令行,并且在UNIX上,我建议使用env
代替shell=True
,例如:
path = '/dir1:/dir2'
subprocess.Popen(['/usr/bin/env', '-P', path, 'progtorun', other, args], ...)
这样可以通过选项-P
向env
进程传递不同的PATH
,以便找到程序。它还避免了与shell元字符和通过shell传递参数时可能出现的安全问题。显然,在Windows上(几乎是唯一没有/usr/bin/env
的平台),您需要采取不同的方法。
关于shell=True
引用Popen
文档:
如果shell为True
,建议将args
作为字符串而不是序列传递。
注意:在使用shell=True
之前,请阅读安全注意事项部分。
意外观察结果
观察到以下行为:
This call raises FileNotFoundError
, as expected:
subprocess.call(['sh'], shell=False, env=dict(PATH=''))
This call finds sh
, which is unexpected:
subprocess.call(['sh'], shell=False, env=dict(FOO=''))
Typing echo $PATH
inside the shell that this opens reveals that the PATH
value is not empty, and also different from the value of PATH
in the environment of Python. So it seems that PATH
was indeed not inherited from Python (as expected in the presence of env != None
), but still, it the PATH
is nonempty. Unknown why this is the case.
This call raises FileNotFoundError
, as expected:
subprocess.call(['tree'], shell=False, env=dict(FOO=''))
This finds tree
, as expected:
subprocess.call(['tree'], shell=False, env=None)
os.environ['PATH']
显式地作为参数env
传递给subprocess.Popen
,就像这里所做的一样:https://dev59.com/oXE95IYBdhLWcg3wn_Rr#4453495和这里所做的一样:https://dev59.com/6GIj5IYBdhLWcg3wERNx#20669704。 - 0 _subprocess.Popen
,它似乎使用shell=False
搜索路径。然而,无效的是使用sys.path.append
增加路径以包括可执行文件的位置 - 我发现只有在Python程序启动之前,%PATH%
包含可执行文件的路径时才有效。 - starfry