簡體   English   中英

python子進程Popen環境路徑?

[英]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/subdir2os.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=Falseshell=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=Trueshell=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 != NonePopen搜索在鍵值PATHenv (如果關鍵PATH出現在env )。

PATH以外的環境變量作為參數傳播

關於PATH以外的環境變量有一個警告:如果命令中需要這些變量的值(例如,作為正在運行的程序的命令行參數),那么即使這些變量存在於提供給Popenenv ,如果沒有shell=True它們將不會被解釋。 這很容易避免,而無需更改shell=True :將這些值直接插入提供給Popenlist參數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文檔:

如果shellTrue ,建議將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)

您似乎對PATHPYTHONPATH的性質有些困惑。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM