【问题标题】:how to make subprocess called with call/Popen inherit environment variables如何使用 call/Popen 调用子进程继承环境变量
【发布时间】:2014-01-07 07:19:14
【问题描述】:

首先,对于我对 bash、shell 和子进程的初步了解,我确信很明显很抱歉。

我正在尝试使用 Python 自动调用名为 Freesurfer 的程序(实际上,我正在调用的子程序称为 recon-all。)

如果我直接在命令行中执行此操作,我将“获取”一个名为 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”的子进程继承它需要的环境变量?或者我怎样才能做我需要做的一切——运行脚本来设置环境变量,并在同一个过程中调用 recon-all?还是我应该以另一种方式解决问题?还是我可能误解了这个问题?

【问题讨论】:

标签: python bash shell environment-variables subprocess


【解决方案1】:

关于

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,然后用one调用这个脚本 subprocess.check_output(而不是多次调用Popencheck_outputcall等):

newscript.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

不管是否可修复,您最好还是构建一个 conglomerate shell 脚本(如上所示)而不是解析 env 和 将env dicts 传递给subprocess 调用。

【讨论】:

  • 我要试试这个!一次只尝试一个解决方案。
  • 效果很好,非常感谢!我将永远记住仅使用 Python 编写单个 bash 脚本的技巧,并且我还从您的回答中学到了一些新的 Python。也许你应该注意这个源函数是如何在这里被破坏的:stackoverflow.com/a/12708396/2066083?这就是我得到它的地方。
【解决方案2】:

如果您查看 Popen 的文档,它需要一个 env 参数:

如果env不是None,则必须是为新进程定义环境变量的映射;使用这些而不是继承当前进程的环境,这是默认行为。

您编写了一个函数,该函数从源脚本中提取所需的环境并将其放入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)

【讨论】:

  • 你说的很有道理。我显然不了解该功能,并且不知何故认为它正在更新环境变量作为副作用。但是,我的问题还没有解决。我得到 env['TEST_ENV_VAR'] 等于 ~/testFreeSurferEnv。到目前为止,一切都很好。但是 check_output(cmd, shell=True, env=env) 仍然失败并出现同样的错误。 call(cmd, shell=True, env=env) 和 Popen(cmd, shell=True, env=env) 不会抛出任何异常,但它们也不会创建 test 子目录。
  • @Katie:mkdir 失败还有其他原因。如果您尝试运行check_output('echo $TEST_ENV_VAR/test', shell=True, env=env),您会返回~/testFreeSurferEnv/test,还是也会失败?当我在 OS X 和 Linux 上使用 Python 2.7 和 3.3 或 3.4 尝试测试(明确设置 env['TEST_ENV_VAR'] 而不是检查所有其他代码,因为您说所有其他代码都有效)时,它在每种情况下都有效。
  • 这没有失败。我想我倾向于认为我的问题没有解决,因为昨晚,当我尝试以正确的方式使用该源函数时,recon-all 命令仍然失败,退出状态为 127。可悲的是,我无法真正调查这个权利现在,因为我使用了 unutbu 建议的“使用 Python 编写单个 bash 脚本”方法来开始我的工作,而此时更改环境变量可能会造成严重破坏。我很想更好地了解潜在的情况,但是我必须不理会它。非常感谢您的帮助。
  • 没有直接的方法可以从子进程中提取所有环境变量吗? @unutbu 的提议相当不错,但我当然希望有一个内置的。
  • @JDong 这是 Unix(以及 Windows NT,就此而言)设计的限制。
猜你喜欢
  • 1970-01-01
  • 2014-01-24
  • 1970-01-01
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多