4

I've been digging into the world of Python and GUI applications and have made some considerable progress. However, I'd like some advice on how to proceed with the following:

  • I've created a GUI application using python (2.6.6 - cannot upgrade system due to it being legacy) and gtk that displays several buttons e.g. app1, app2, app3
  • When I click on a button, it then runs a bash shell script. This script will set up some required environment variables and then execute another external application (that uses these env variables)

Example:
1) use clicks on button app1
2) GUI then launches app1.sh to set up environment variables
3) GUI then runs external_app1

# external_app1 is an example application
# that requires that some environment
# variables to be set before it can launch


Example app1.sh contents:

#/bin/bash

export DIR=/some/location/
export LICENSE=/some/license/
export SOMEVAR='some value'


NOTE: Due to the way the environment is configured, it has to launch shell scripts first to set up the environment etc, and then launch the external applications. The shell scripts will be locked down so it cannot be edited by anyone once I've tested them.


So I've thought about how to have the python GUI execute this and so far, I am doing the following:

  • When user clicks on app1, check if app1.sh is executable/readable, if not, return error
  • Create another helper script, let's say helper1.sh that will contain the app1.sh followed by the external_app1 command and then have python execute that helper1.sh script via the below:

subprocess.Popen(helper1.sh, shell=True, stdout=out, stderr=subprocess.PIPE, close_fds=True)


Example helper1.sh contents:

#!/usr/bin/env bash

source app1.sh   # sets up env variables

if [ $? = 0 ]; then
    external_app &    # Runs the actual application in background
else
    echo "Error executing app1.sh" 2>/dev/stderr
fi


This is done so that the helper script executes in its own subshell and so that I can run multiple environment setup / external applications (app2, app3 etc).

So I ask:

  1. Is there a better perhaps more pythonic way of doing this? Can someone point me in the right direction?
  2. And when it comes to logging and error handling, how to effectively capture stderr or stdout from the helper scripts (e.g. helper1.sh) without blocking/freezing the GUI? Using threads or queues?


Thank you.

  • would these answers help? subprocess seems to handle environment variables and execution... http://stackoverflow.com/questions/26236126/how-to-run-bash-command-inside-python-script – rupert160 Sep 30 '15 at 10:23

1 Answers1

0

As I understand your question, you're trying to execute an external command with one of n sets of environment variables, where n is specified by the user. The fact that it's a GUI application doesn't seem relevant to the problem. Please correct me if I'm missing something.

You have several choices:

Execute a command in Python with custom environment variables

Rather than store the environment variables in separate files, you can set them directly with the env argument to Popen():

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.

So instead of having app1.sh, app2.sh, app3.sh, and so on, store your environment variable sets in Python, and add them to the environment you pass to Popen(), like so:

env_vars = {
  1: {
    'DIR': '/some/location/',
    'LICENSE': '/some/license/'
    'SOMEVAR': 'some value'
  },
  2: ...
}

...

environ_copy = dict(os.environ)
environ_copy.update(env_vars[n])
subprocess.Popen('external_application', shell=True, env=environ_copy, ...)

Modify the environment with a wrapper script

If your environment vars must live in separate, dedicated shell scripts something like your helper is the best you can do.

We can clean it up a little, though:

#!/usr/bin/env bash

if source "$1"; then  # Set up env variables
    external_app      # Runs the actual application
else
    echo "Error executing $1" 2>/dev/stderr
    exit 1            # Return a non-zero exit status
fi

This lets you pass app1.sh to the script, rather than create n separate helper files. It's not clear why you're using & to background the process - Popen starts a separate process which doesn't block the Python process from continuing. With subprocess.PIPE you can use Popen.communicate() to get back the process' stdout and stderr.

Avoid setting environment variables at all

If you have control of external_process (i.e. you wrote it, and can modify it), you'd be much better off changing it to use command line arguments, rather than environment variables. That way you could call:

subprocess.Popen('external_command', '/some/location/', '/some/license/', 'some value')

and avoid needing shell=True or a wrapper script entirely. If external_command expects a number of variables it might be better to use --flags (e.g. --dir /some/location/) rather than positional arguments. Most programming languages have a argument processing library (or several) to make this easy; Python provides argparse for this purpose.

Using command line arguments rather than environment variables will make external_process much more user friendly, especially for the use case you're describing. This is what I would suggest doing.

dimo414
  • 47,227
  • 18
  • 148
  • 244