2011-04-14 316 views
37

我對subprocess在使用Popen()時如何搜索可執行文件感到困惑。它的作品,如果給予子進程的絕對路徑,但我試圖使用相對路徑。我發現如果我設置了環境變量PYTHONPATH,那麼我可以從該路徑獲取導入的模塊,PYTHONPATH在sys.path中,但它似乎對subprocess.Popen的行爲沒有幫助。我也試着編輯sitecustomize.py文件中添加PYTHONPATH到os.environ,像這樣python subprocess Popen環境PATH?

# 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')]) 

並驗證啓動蟒蛇,無論是在交互,與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

+1

關於重讀這個問題,我想我看到了這個問題。在命令行shell中,切換到'/ dir'並查看如果輸入'../subdir1/some_executable'會出現什麼情況。 – ncoghlan 2011-04-14 06:24:08

+0

好吧,我明白你在說什麼,我的誤解是假設相對路徑會像裸機程序一樣被搜索到。謝謝 – wim 2011-04-14 07:08:50

回答

45

(從評論填充細節,製作一個單獨的答案)

首先,相對路徑(含斜槓路徑)從來沒有在任何路徑得到遏制,不管你做什麼。它們僅與當前工作目錄有關。如果需要解析相對路徑,則必須手動搜索PATH,或者將PATH包含到子目錄中,然後按照我的建議使用命令名稱。

如果你想運行相對於Python腳本的位置的程序,使用__file__並從那裏找到該程序的絕對路徑,然後使用絕對路徑Popen

其次,有關於Python如何處理裸指令(無斜線)的an issue in the Python bug tracker。基本上,在Unix時shell=False調用,這意味着它着眼於PATH的價值,因爲它是在Python的推出並沒有改變os.environ量將幫助你修復/ Mac的Popen使用os.execvp。另外,在使用shell=False的Windows上,根本不關注PATH,只會查看與當前工作目錄相關的信息。

如果你只需要路徑評估,並不能真正想通過一個shell來運行你的命令行,並在UNIX上,我建議使用env代替shell=True,如Popen(['/usr/bin/env', 'progtorun', other, args], ...)。這可讓您將不同的PATH傳遞給env進程,該進程將使用它來查找程序。它還避免了shell元字符的問題以及通過shell傳遞參數的潛在安全問題。顯然,在Windows上(幾乎沒有/usr/bin/env的唯一平臺),你需要做一些不同的事情。

+1

非常明確和有用的解釋,謝謝 – wim 2011-04-20 06:06:55

+3

+1「另外,在shell = False的Windows上,根本不關注PATH,只會查看當前工作目錄。」剛剛幫我解決了一個大問題 - 謝謝! – 2013-10-09 20:03:55

+1

在Windows上工作的一個簡單方法是將'os.environ ['PATH']'作爲參數'env'明確地賦予'subprocess.Popen',如下所示:http://stackoverflow.com/a/4453495/1959808和那裏:http://stackoverflow.com/a/20669704/1959808。 – 2016-06-30 12:02:33

0

pythonpath被設置爲執行python解釋器的路徑。因此,在您的示例的第二種情況下,路徑設置爲/ dir而不是/ dir/subdir2 這就是爲什麼會出現錯誤。

+1

我不相信這是正確的,因爲如果我編寫一個簡單的腳本來打印os.environ,那麼無論從哪裏運行解釋器,PYTHONPATH都是一樣的。 PYTHONPATH在/ etc/environment中設置,用於擴充模塊的搜索路徑 – wim 2011-04-14 04:57:41

+0

我的意思是說從中執行python的目錄,該目錄被添加到pythonpath中。在第二種情況下,/ dir被添加,而不是/ dir/subdir2。因此,您可以更改代碼以反映更改(一種方法可以是將/ dir/subdir2添加到代碼中的os.path中),也可以從相應的目錄啓動python。 – c0da 2011-04-14 05:48:14

8

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

PATH是一個環境變量,它告訴OS shell在哪裏搜索可執行文件。

PYTHONPATH是一個環境變量,它告訴Python解釋器在哪裏搜索要導入的模塊。它與找到可執行文件的subprocess無關。

由於底層實現的差異,subprocess.Popen只會在非Windows系統上默認搜索路徑(Windows有一些系統目錄,它總是搜索,但不同於PATH處理)。唯一可靠的跨平臺的方式來掃描路徑是通過傳遞shell=True到子打電話,但它有自己的問題

但是(如Popen documentation詳述),它出現在您的主要問題是要傳遞一個路徑片段爲Popen而不是簡單的文件名。只要您有目錄分隔符,即使在非Windows平臺上(例如,請參閱exec family of functions的Linux文檔),您也將禁用搜索功能PATH

+2

這與Python文檔不匹配。 [The Popen docs](http://docs.python.org/library/subprocess.html)聲明該程序是通過'os.execvp'執行的,並且該調用會考慮PATH環境變量。另外,如果你只需要路徑評估,我建議使用'env'而不是'shell = True',就像'Popen(['/ usr/bin/env','progtorun',other,args],... )'。這可以避免shell元字符的問題,以及通過shell傳遞參數時可能存在的安全問題。 – 2011-04-14 06:03:07

+1

這些都是* NIX特定的,但它們不適用於Windows,所以我不喜歡推薦它們作爲名義上跨平臺模塊的解決方法。你說的正確,我的答案是不正確的 - 會做相應的編輯。 – ncoghlan 2011-04-14 06:17:34

+2

經過更新,可以明確指出默認情況下不搜索PATH是僅限於Windows的事情,但也指出了真正的問題(要執行的命令中的目錄分隔符)。 – ncoghlan 2011-04-14 06:36:02

2

子流程中的相對路徑。Popen相對於當前工作目錄而不是系統PATH的元素。如果您從/dir運行python subdir2/some_script.py比預期的可執行位置將/dir/../subdir2/some_executable,又名/subdir2/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()