[英]python subprocess Popen environment PATH?
我對使用Popen()
時subprocess
Popen()
如何搜索可執行文件感到困惑。 如果給定子進程的絕對路徑,它就可以工作,但我正在嘗試使用相對路徑。 我發現如果我設置了環境變量 PYTHONPATH 那么我可以從那個路徑中獲取導入的模塊,並且 PYTHONPATH 在sys.path
,但它似乎對subprocess.Popen
的行為沒有幫助。 我還嘗試編輯sitecustomize.py
文件,將 PYTHONPATH 添加到os.environ
,就像這樣
# copy PYTHONPATH environment variable into PATH to allow our stuff to use
# relative paths for subprocess spawning
import os
if os.getenv('PYTHONPATH') is not None and os.getenv('PATH') is not none:
os.environ['PATH'] = ':'.join([os.getenv('PATH'), os.getenv('PYTHONPATH')])
並驗證在啟動 python 時,無論是交互方式,使用 ipython,還是通過從命令行運行腳本,PYTHONPATH 成功出現在os.environ
。 但是, subrocess.Popen
仍然不會在那里搜索可執行文件。 如果沒有指定env
kwarg,我認為它應該繼承父環境? 接下來我嘗試明確地給出env
,首先通過制作os.getenv
的副本,其次只是通過給出env={'PATH': '/explicit/path/to/search/from'}
,它仍然沒有找到可執行文件. 現在我很難過。
希望一個例子將有助於更清楚地解釋我的問題:
/dir/subdir1/some_executable
/dir/subdir2/some_script.py
# some_script.py
from subprocess import Popen, PIPE
spam, eggs = Popen(['../subdir1/some_executable'], stdout=PIPE, stderr=PIPE).communicate()
如果我在/dir/subdir2
並且我運行python some_script.py
它可以工作,但是如果我在/dir
並且我運行python subdir2/some_script.py
即使/dir/subdir2
在os.environ['PATH']
,然后子進程將拋出OSError: [Errno 2] No such file or directory
。
(從評論中填寫詳細信息以單獨回答)
首先,無論您做什么,都不會在任何PATH
檢查相對路徑(包含斜杠的路徑)。 它們僅相對於當前工作目錄。 如果您需要解析相對路徑,則必須手動搜索PATH
,或者修改PATH
以包含子目錄,然后只需使用我下面的建議中的命令名稱。
如果要運行相對於 Python 腳本位置的程序,請使用__file__
並從那里找到程序的絕對路徑,然后使用Popen
的絕對路徑。
PATH
其次, Python 錯誤跟蹤器中存在關於 Python 如何處理裸命令(無斜杠)的問題。 基本上,在Unix / Mac的Popen
行為就像os.execvp
當參數env=None
(一些意想不到的行為已經觀察到,並指出在最后):
在 POSIX 上,該類使用
os.execvp()
類的行為來執行子程序。
這對於shell=False
和shell=True
實際上都是shell=True
,前提是env=None
。 函數os.execvp
的文檔中解釋了此行為的含義:
在末尾包含“p”的變體(
execlp()
、execlpe()
、execvp()
和execvpe()
)將使用PATH
環境變量來定位程序文件。 當環境被替換時(使用exec*e
變體之一,在下一段中討論),新環境被用作PATH
變量的源。
對於
execlpe()
execle()
、execlpe()
、execve()
和execvpe()
(注意這些都以“e”結尾), env參數必須是一個映射,用於定義新進程的環境變量(這些用於代替當前進程的環境); 函數execl()
、execlp()
、execv()
和execvp()
都會導致新進程繼承當前進程的環境。
引用的第二段暗示execvp
將使用當前進程的環境變量。 結合第一個引用的段落,我們推斷execvp
會從當前進程的環境中使用環境變量PATH
的值。 這意味着Popen
查看PATH
的值,因為它在 Python 啟動時(運行Popen
實例化的 Python)並且沒有任何更改os.environ
可以幫助您解決這個問題。
此外,在帶有shell=False
Windows 上, Popen
根本不關注PATH
,只會查看相對於當前工作目錄的內容。
shell=True
做什么如果我們將shell=True
傳遞給Popen
會發生什么? 在這種情況下, Popen
只需調用 shell :
shell參數(默認為
False
)指定是否使用 shell 作為要執行的程序。
也就是說,
Popen
作用相當於:Popen(['/bin/sh', '-c', args[0], args[1], ...])
換句話說,with shell=True
Python 會直接執行/bin/sh
,無需任何搜索(將參數executable
傳遞給Popen
可以改變這一點,而且看起來如果它是一個沒有斜線的字符串,那么它會被 Python 解釋作為 shell 程序的名稱,在當前進程的環境中在PATH
的值中搜索,即,當它在上述shell=False
的情況下搜索程序時)。
反過來, /bin/sh
(或我們的 shell executable
)將在其自身環境的PATH
查找我們想要運行的程序,該程序與 Python(當前進程)的PATH
相同,從后面的代碼推斷上面的短語“也就是說...”(因為該調用具有shell=False
,因此前面已經討論過這種情況)。 因此,只要env=None
,我們就可以通過shell=True
和shell=False
類似execvp
的行為。
env
傳遞給Popen
那么如果我們將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
給出的程序的搜索路徑是PATH
通過env
傳遞給Popen
的值。
因此,與env != None
, Popen
搜索在鍵值PATH
的env
(如果關鍵PATH
出現在env
)。
PATH
以外的環境變量作為參數傳播關於PATH
以外的環境變量有一個警告:如果命令中需要這些變量的值(例如,作為正在運行的程序的命令行參數),那么即使這些變量存在於提供給Popen
的env
,如果沒有shell=True
它們將不會被解釋。 這很容易避免,而無需更改shell=True
:將這些值直接插入提供給Popen
的list
參數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], ...)
這使您可以將不同的PATH
傳遞給env
進程(使用選項-P
),它將使用它來查找程序。 它還避免了 shell 元字符問題和通過 shell 傳遞參數的潛在安全問題。 顯然,在 Windows(幾乎是唯一沒有/usr/bin/env
)上,您需要做一些不同的事情。
shell=True
引用Popen
文檔:
如果shell是
True
,建議將args
作為字符串而不是序列傳遞。
注意:在使用
shell=True
之前閱讀安全注意事項部分。
觀察到以下行為:
正如預期的那樣,此調用引發FileNotFoundError
:
subprocess.call(['sh'], shell=False, env=dict(PATH=''))
這個調用找到了sh
,這是出乎意料的:
subprocess.call(['sh'], shell=False, env=dict(FOO=''))
打字echo $PATH
這將打開外殼里面揭示了PATH
值不為空,並從價值也不同PATH
在Python的環境。 因此,似乎PATH
確實不是從 Python 繼承的(正如在env != None
存在的情況下所預期的那樣),但是,它PATH
仍然是非空的。 不知道為什么會這樣。
正如預期的那樣,此調用引發FileNotFoundError
:
subprocess.call(['tree'], shell=False, env=dict(FOO=''))
這會發現tree
,正如預期的那樣:
subprocess.call(['tree'], shell=False, env=None)
您似乎對PATH
和PYTHONPATH
的性質有些困惑。
PATH
是一個環境變量,它告訴 OS shell 在哪里搜索可執行文件。
PYTHONPATH
是一個環境變量,它告訴 Python 解釋器在哪里搜索要導入的模塊。 它與subprocess
進程查找可執行文件無關。
由於底層實現的差異, subprocess.Popen
只會在非 Windows 系統上默認搜索路徑(Windows 有一些它總是搜索的系統目錄,但這與PATH
處理不同)。 掃描路徑的唯一可靠的跨平台方法是將shell=True
傳遞給子進程調用,但這有其自身的問題(如Popen
文檔中所述)
但是,您的主要問題似乎是您將路徑片段傳遞給Popen
而不是簡單的文件名。 只要您在那里有了目錄分隔符,您就會禁用PATH
搜索,即使在非 Windows 平台上也是如此(例如,請參閱exec 函數系列的 Linux 文檔)。
subprocess.Popen 中的相對路徑相對於當前工作目錄起作用,而不是系統路徑的元素。 如果您從/dir
運行python subdir2/some_script.py
那么預期的可執行位置(傳遞給/dir/../subdir1/some_executable
)將是/dir/../subdir1/some_executable
,又名/subdir1/some_executable
而不是/dir/subdir1/some_executable
。
如果您肯定想使用從腳本自己的目錄到特定可執行文件的相對路徑,最好的選擇是首先從__file__
全局變量的目錄部分構造一個絕對路徑。
#/usr/bin/env python
from subprocess import Popen, PIPE
from os.path import abspath, dirname, join
path = abspath(join(dirname(__file__), '../subdir1/some_executable'))
spam, eggs = Popen(path, stdout=PIPE, stderr=PIPE).communicate()
pythonpath 設置為執行 python 解釋器的路徑。 因此,在您的示例的第二種情況下,路徑設置為 /dir 而不是 /dir/subdir2 這就是您收到錯誤的原因。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.