2013-12-18 47 views
5

首先,我肯定會明白的道歉是我對bash和shell和subprocesses的基本理解。如何通過調用/ Popen調用子進程繼承環境變量

我試圖使用Python自動到一個名爲Freesurfer程序調用(實際上,我打電話調用子程序偵察 - 所有。)

如果我在命令行直接這樣做,我'd「source」一個名爲mySetUpFreeSurfer.sh的腳本,它只會設置三個環境變量,然後「源」另一個腳本FreeSurferEnv.sh。 FreesurferEnv.sh在我看來並沒有做任何事情,只是設置了很多環境變量,並在終端上回顯了一些東西,但它比其他bash腳本更復雜,所以我不確定這一點。

這是我現在所擁有的:

from subprocess import Popen, PIPE, call, check_output 
import os 

root = "/media/foo/" 

#I got this function from another Stack Overflow question. 

def source(script, update=1): 
    pipe = Popen(". %s; env" % script, stdout=PIPE, shell=True) 
    data = pipe.communicate()[0] 
    env = dict((line.split("=", 1) for line in data.splitlines())) 
    if update: 
     os.environ.update(env) 
    return env 

source('~/scripts/mySetUpFreeSurfer.sh') 
source('/usr/local/freesurfer/FreeSurferEnv.sh') 

for sub_dir in os.listdir(root): 
    sub = "s" + sub_dir[0:4] 
    anat_dir = os.path.join(root, sub_dir, "anatomical") 
    for directory in os.listdir(anat_dir): 
     time_dir = os.path.join(anat_dir, directory) 
     for d in os.listdir(time_dir): 
      dicoms_dir = os.path.join(time_dir, d, 'dicoms') 
      dicom_list = os.listdir(dicoms_dir) 
      dicom = dicom_list[0] 
      path = os.path.join(dicoms_dir, dicom) 
      cmd1 = "recon-all -i " + path + " -subjid " + sub 
      check_output(cmd1, shell=True) 
      call(cmd1, shell=True) 
      cmd2 = "recon-all -all -subjid " + sub, 
      call(cmd2, shell=True) 

這是失敗的:

Traceback (most recent call last): 
    File "/home/katie/scripts/autoReconSO.py", line 28, in <module> 
     check_output(cmd1, shell=True) 
     File "/usr/lib/python2.7/subprocess.py", line 544, in check_output 
     raise CalledProcessError(retcode, cmd, output=output) 
    CalledProcessError: Command 'recon-all -i /media/foo/bar -subjid s1001' returned non-zero exit status 127 

我也許明白這是爲什麼。我稍後在腳本中的「調用」提出了新的子進程,它們不會從調用source()函數引發的進程繼承環境變量。我做了很多事情來試圖確認我的理解。一個例子 - 我把這些行:

mkdir ~/testFreeSurferEnv 
export TEST_ENV_VAR=~/testFreeSurferEnv 

在FreeSurferEnv.sh腳本。該目錄被製作得很好,但在Python腳本這樣的:

cmd = 'mkdir $TEST_ENV_VAR/test' 
check_output(cmd, shell=True) 

失敗是這樣的:

File "/usr/lib/python2.7/subprocess.py", line 544, in check_output 
    raise CalledProcessError(retcode, cmd, output=output) 
CalledProcessError: Command 'mkdir $TEST_ENV_VAR/test' returned non-zero exit status 1 

問題:

我怎樣才能讓運行子進程「偵察「所有」繼承它需要的環境變量?或者我該怎麼做我需要做的一切 - 運行腳本來設置環境變量,並在同一個進程中調用recon-all?還是應該以另一種方式處理問題?或者我可能誤解了這個問題?

+0

相關:[調用從subprocess.Popen「源」命令(http://stackoverflow.com/q/7040592/4279) – jfs

回答

4

關於

If I were doing this directly at the command line, I'd "source" a script called mySetUpFreeSurfer.sh that does nothing but set three environment variables, and then "source" another script, FreeSurferEnv.sh.

我想你會用Python自動編寫 shell腳本newscript.sh,然後調用這個腳本一個的過程中會更好請致電 subprocess.check_output(而不是撥打Popencheck_outputcall等):

新聞稿。SH:

#!/bin/bash 
source ~/scripts/mySetUpFreeSurfer.sh 
source /usr/local/freesurfer/FreeSurferEnv.sh 
recon-all -i /media/foo/bar -subjid s1001 
... 

,然後調用

subprocess.check_output(['newscript.sh']) 

import subprocess 
import tempfile 
import os 
import stat 


with tempfile.NamedTemporaryFile(mode='w', delete=False) as f: 
    f.write('''\ 
#!/bin/bash 
source ~/scripts/mySetUpFreeSurfer.sh 
source /usr/local/freesurfer/FreeSurferEnv.sh 
''') 
    root = "/media/foo/" 
    for sub_dir in os.listdir(root): 
     sub = "s" + sub_dir[0:4] 
     anat_dir = os.path.join(root, sub_dir, "anatomical") 
     for directory in os.listdir(anat_dir): 
      time_dir = os.path.join(anat_dir, directory) 
      for d in os.listdir(time_dir): 
       dicoms_dir = os.path.join(time_dir, d, 'dicoms') 
       dicom_list = os.listdir(dicoms_dir) 
       dicom = dicom_list[0] 
       path = os.path.join(dicoms_dir, dicom) 
       cmd1 = "recon-all -i {} -subjid {}\n".format(path, sub) 
       f.write(cmd1) 
       cmd2 = "recon-all -all -subjid {}\n".format(sub) 
       f.write(cmd2) 

filename = f.name 
os.chmod(filename, stat.S_IRUSR | stat.S_IXUSR) 
subprocess.call([filename]) 
os.unlink(filename) 

順便說一句,

def source(script, update=1): 
    pipe = Popen(". %s; env" % script, stdout=PIPE, shell=True) 
    data = pipe.communicate()[0] 
    env = dict((line.split("=", 1) for line in data.splitlines())) 
    if update: 
     os.environ.update(env) 
    return env 

壞了。例如,如果script包含有類似

VAR=`ls -1` 
export VAR 

然後

. script; env 

可能會返回像

VAR=file1 
file2 
file3 

輸出,這將導致source(script)ValueError

env = dict((line.split("=", 1) for line in data.splitlines())) 
ValueError: dictionary update sequence element #21 has length 1; 2 is required 

有固定source的方式:有一個零字節,而不是模棱兩可的換行符env單獨的環境變量:

def source(script, update=True): 
    """ 
    http://pythonwise.blogspot.fr/2010/04/sourcing-shell-script.html (Miki Tebeka) 
    http://stackoverflow.com/questions/3503719/#comment28061110_3505826 (ahal) 
    """ 
    import subprocess 
    import os 
    proc = subprocess.Popen(
     ['bash', '-c', 'set -a && source {} && env -0'.format(script)], 
     stdout=subprocess.PIPE, shell=False) 
    output, err = proc.communicate() 
    output = output.decode('utf8') 
    env = dict((line.split("=", 1) for line in output.split('\x00') if line)) 
    if update: 
     os.environ.update(env) 
    return env 

可固定或沒有,但是,你仍然可能會更好過構建 集成shell腳本(如上所示)比解析env和 通過env字典到subprocess調用。

+0

我要試試這個!一次只嘗試一個解決方案。 – Katie

+0

這很好,非常感謝!我會記住使用Python永遠編寫單個bash腳本的技巧,並且我還從您的答案中學習了一些新的Python。也許你應該注意到這個源代碼函數是如何在這裏打破的:http://stackoverflow.com/a/12708396/2066083?這就是我得到它的地方。 – Katie

12

如果你看看文檔爲Popen,它需要一個env參數:

如果ENVNone,它必須定義爲新進程的環境變量的映射;這些用來代替繼承當前進程的環境,這是默認行爲。

您已經編寫了一個函數,可以從源腳本中提取所需的環境並將其放入dict。只需將結果作爲env傳遞給您要使用它的腳本。例如:

env = {} 
env.update(os.environ) 
env.update(source('~/scripts/mySetUpFreeSurfer.sh')) 
env.update(source('/usr/local/freesurfer/FreeSurferEnv.sh')) 

# … 

check_output(cmd, shell=True, env=env) 
+0

你說什麼讓人感覺很好。我顯然不明白這個功能,並且不知何故已經意識到它正在更新環境變量作爲副作用。 但是,我的問題還沒有解決。我得到env ['TEST_ENV_VAR']等於〜/ testFreeSurferEnv。到現在爲止還挺好。但是check_output(cmd,shell = True,env = env)仍然會失敗並顯示相同的錯誤。調用(cmd,shell = True,env = env)和Popen(cmd,shell = True,env = env)不會拋出任何類型的異常,但它們也不會生成測試子目錄。 – Katie

+0

@Katie:還有其他原因'mkdir'可能會失敗。如果你嘗試運行'check_output('echo $ TEST_ENV_VAR/test',shell = True,env = env)',你會回到'〜/ testFreeSurferEnv/test',還是會失敗呢?當我在Python 2.7和3.3或3.4的OS X和Linux上嘗試測試(顯式設置'env ['TEST_ENV_VAR']'而不是通過所有其他代碼,因爲你說所有其他代碼都能正常工作)適用於任何情況。 – abarnert

+0

這並不是失敗。我想我傾向於認爲我的問題沒有解決,因爲昨天晚上,當我嘗試以正確的方式使用源函數時,recon-all命令仍然失敗,退出狀態爲127.可悲的是,我無法真正調查這個權利現在,因爲我使用unutbu建議的「使用Python編寫單個bash腳本」方法來啓動我的工作,並且此時更改環境變量可能會造成嚴重破壞。我很好奇想要更好地理解潛在的情況,但是我必須保持獨立。我非常感謝幫助。 – Katie