how to make subprocess called with call/Popen inherit environment variables how to make subprocess called with call/Popen inherit environment variables bash bash

how to make subprocess called with call/Popen inherit environment variables


If you look at the docs for Popen, it takes an env parameter:

If env is not None, it must be a mapping that defines the environment variables for the new process; these are used instead of inheriting the current process’ environment, which is the default behavior.

You've written a function that extracts the environment you want from your sourced scripts and puts it into a dict. Just pass the result as the env to the scripts you want to use it. For example:

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)


Regarding

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.

I think you would be better off using Python to automate the process of writinga shell script newscript.sh, and then calling this script with one callsubprocess.check_output (instead of many calls to Popen, check_output,call, etc.):

newscript.sh:

#!/bin/bashsource ~/scripts/mySetUpFreeSurfer.shsource /usr/local/freesurfer/FreeSurferEnv.shrecon-all -i /media/foo/bar -subjid s1001...

and then calling

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

import subprocessimport tempfileimport osimport statwith tempfile.NamedTemporaryFile(mode='w', delete=False) as f:    f.write('''\#!/bin/bashsource ~/scripts/mySetUpFreeSurfer.shsource /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.nameos.chmod(filename, stat.S_IRUSR | stat.S_IXUSR)subprocess.call([filename])os.unlink(filename)

By the way,

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

is broken. For example, if script contains something like

VAR=`ls -1`export VAR

then

. script; env

may return output like

VAR=file1file2file3

which will result in source(script) raising a ValueError:

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

There is a way to fix source: have env separate environment variables with a zero byte instead of the ambiguous newline:

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

Fixable or not, however, you are still probably better off constructing aconglomerate shell script (as shown above) than you would be parsing env andpassing env dicts to subprocess calls.