Cross platform spawning

Trent Mick trentm at
Tue Jun 11 18:08:23 CEST 2002

[Peter Scott wrote]
> I am trying to make a script spawn a seperate process, like you would
> do like this on unix:
> os.system('python myargument &')
> But the & at the end (to make a separate process) doesn't work on
> Windows. I want the script to run on both Linux (*BSD, whatever) and
> Windows. I didn't want to have to find out the path to the python
> interpreter, so os.spawnlp (with no blocking) sounded perfect until I
> saw that it wasn't available on Windows.
> Does anyone out there know what I can do in this situation?

You could try to use os.spawnl(), which *is* availble on Windows. The
only pain is that you have to manually determine the full path to the
executable yourself via some which-like algorithm. I have attached a
script of mine that can do that on Windows and Linux (though I haven't
teseted it a *whole* lot.


Trent Mick
TrentM at
-------------- next part --------------
#!/usr/bin/env python

    Show full path of commands.

        which [<options>...] [<command-name>...]

        -h, --help      Print this help and exit.
        -a, --all       Print *all* matching paths.
        -v, --verbose   Print out near misses on stderr.
        --version       Print the version info and exit.

    Show the full path to the program that would be run for each given
    command name, if any. Which, like GNU's which, returns the number of
    failed arguments, or -1 when no <command-name> was given.
#   - Python >= 2.2 (because of the use of generators)
#   - improve the test suite
#   * launcher app? make the launcher app generic, it should be able to
#     use argv[0] to determine what to launch, then it can be built and
#     just renamed.
#   * explain what *near* misses are, perhaps that should be the default
#     and -q can be used to suppress them. I think so, yes.

from __future__ import generators
import os
import sys
import getopt
import stat

#---- exceptions

class WhichError(Exception):

#---- global data

_version_ = (0, 1, 2)
gVerbose = 0

#---- internal support stuff

def _getRegisteredExecutable(exeName):
    """Windows allow application paths to be registered in the registry."""
    if sys.platform.startswith('win'):
        if os.path.splitext(exeName)[1].lower() != '.exe':
            exeName += '.exe'
        import _winreg
            return _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE,
                "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\"\
                + exeName)
        except _winreg.error:
    return None

def _samefile(fname1, fname2):
    if sys.platform.startswith('win'):
        return ( os.path.normpath(os.path.normcase(fname1)) ==\
            os.path.normpath(os.path.normcase(fname2)) )
        return os.path.samefile(fname1, fname2)

def _cull(potential, matches):
    """Cull inappropriate matches. Possible reasons:
        - a duplicate of a previous match
        - not a disk file
        - not executable (non-Windows)
    If 'potential' is approved it is returned and added to 'matches'.
    Otherwise, None is returned.
    global gVerbose
    for match in matches:  # don't yield duplicates
        if _samefile(potential, match):
            if gVerbose:
                sys.stderr.write("duplicate: %s\n" % potential)
            return None
        if not stat.S_ISREG(os.stat(potential).st_mode):
            if gVerbose:
                sys.stderr.write("not a regular file: %s\n" % potential)
        elif not os.access(potential, os.X_OK):
            if gVerbose:
                sys.stderr.write("no executable access: %s\n" % potential)
            return potential

#---- public stuff

def which(command):
    """Generate a list of found apps for the given 'command' on the path."""
    matches = []
    path = os.environ.get("PATH", "").split(os.pathsep)
    if sys.platform.startswith("win"):
        path.insert(0, os.curdir)  # implied by Windows shell
    # Windows has the concept of a list of extensions (PATHEXT env var).
    exts = []
    if sys.platform.startswith("win"):
        exts = os.environ.get("PATHEXT", "").split(os.pathsep)
        # If '.exe' is not in exts then obviously this is Win9x and or a
        # bogus PATHEXT, then use a reasonable default.
        for ext in exts:
            if ext.lower() == ".exe":
            exts = ['.COM', '.EXE', '.BAT']
    # File name cannot have path separators because PATH lookup does not
    # work that way.
    if os.sep in command or os.altsep and os.altsep in command:
        for dirName in path:
            # On windows the dirName *could* be quoted, drop the quotes
            if sys.platform.startswith("win") and len(dirName) >= 2\
               and dirName[0] == '"' and dirName[-1] == '"':
                dirName = dirName[1:-1]
            for ext in ['']+exts:
                absName = os.path.abspath(
                    os.path.normpath(os.path.join(dirName, command+ext)))
                if os.path.isfile(absName):
                    match = _cull(absName, matches)
                    if match:
                        yield match
        absName = _getRegisteredExecutable(command)
        if absName is not None:
            match = _cull(absName, matches)
            if match:
                yield match

#---- mainline

def main(argv):
    global gVerbose, _version_
    all = 0
        optlist, args = getopt.getopt(argv[1:], 'hav',
                                      ['help', 'all', 'version', 'verbose'])
    except getopt.GetoptError, msg:
        print "which: error: %s. Your invocation was: %s\n" % (msg, argv)
        print __doc__
        return 1
    for opt, optarg in optlist:
        if opt in ('-h', '--help'):
            print __doc__
            return 0
        elif opt == '--version':
            print "Which %s" % '.'.join([str(i) for i in _version_])
            return 0
        elif opt in ('-a', '--all'):
            all = 1
        elif opt in ('-v', '--verbose'):
            gVerbose = 1

    if len(args) == 0:
        return -1

    failures = 0
    for arg in args:
        #print "debug: search for %r" % arg
        npaths = 0
        for path in which(arg):
            print path
            npaths += 1
            if not all:
        if not npaths:
            failures += 1
    return failures

if __name__ == "__main__":
    sys.exit( main(sys.argv) )

More information about the Python-list mailing list