cómo hacer que el subproceso llamado con call / Popen herede las variables de entorno

En primer lugar, las disculpas por lo que estoy seguro de que será obvio es mi comprensión rudimentaria de bash, shells y subprocesses.

Estoy tratando de usar Python para automatizar las llamadas a un progtwig llamado Freesurfer (en realidad, el subprogtwig al que estoy llamando se llama recon-all).

Si estuviera haciendo esto directamente en la línea de comandos, “fuente” un script llamado mySetUpFreeSurfer.sh que no hace nada más que establecer tres variables de entorno, y luego “fuente” otro script, FreeSurferEnv.sh. FreesurferEnv.sh no me parece que haga nada, pero establece muchas variables de entorno y hace eco de algunas cosas en el terminal, pero es más complicado que el otro script de bash, así que no estoy seguro de eso.

Aquí está lo que tengo ahora:

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) 

Esto está fallando:

 Traceback (most recent call last): File "/home/katie/scripts/autoReconSO.py", line 28, in  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 

Tal vez entiendo por qué esto es. Mis “llamadas” más adelante en el script están generando nuevos subprocesos que no heredan las variables de entorno de los procesos que son provocados por la invocación de la función source (). He hecho una serie de cosas para intentar confirmar mi comprensión. Un ejemplo – Pongo estas líneas:

 mkdir ~/testFreeSurferEnv export TEST_ENV_VAR=~/testFreeSurferEnv 

en el script FreeSurferEnv.sh El directorio se hace muy bien, pero en el script de Python esto:

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

falla así:

 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 

PREGUNTA:

¿Cómo puedo hacer que el subproceso que ejecuta “recon-all” herede las variables de entorno que necesita? O, ¿cómo puedo hacer todo lo que necesito hacer: ejecutar los scripts para establecer las variables de entorno y llamar a recon-all, en el mismo proceso? ¿O debería abordar el problema de otra manera? ¿O es probable que malinterprete el problema?

Respecto a

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.

Creo que sería mejor utilizar Python para automatizar el proceso de escribir un script de shell newscript.sh y luego llamar a este script con una llamada subprocess.check_output (en lugar de muchas llamadas a Popen , check_output , call , etc.):

newscript.sh:

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

y luego llamando

 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) 

Por cierto,

 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 

está roto. Por ejemplo, si el script contiene algo como

 VAR=`ls -1` export VAR 

entonces

 . script; env 

puede devolver la salida como

 VAR=file1 file2 file3 

que resultará en que la source(script) ValueError un ValueError :

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

Hay una forma de corregir el source : tener variables de entorno env separadas con un byte cero en lugar de la nueva línea ambigua:

 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 

Sin embargo, se puede corregir o no, es probable que todavía esté mejor construyendo un script de conglomerado de conglomerado (como se muestra arriba) que si estuviera analizando env y pasando env dts a llamadas de subprocess .

Si miras los documentos de Popen , toma un parámetro env :

Si env no es None , debe ser una asignación que defina las variables de entorno para el nuevo proceso; estos se utilizan en lugar de heredar el entorno del proceso actual, que es el comportamiento predeterminado.

Ha escrito una función que extrae el entorno que desea de sus scripts de origen y lo pone en un dict . Simplemente pase el resultado como env a los scripts que desea usar. Por ejemplo:

 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)