Q: how to "spawn" an external cmd and catch its stdin,stdout,stderr (on Win32)

Pat Knight p.knight at ktgroup.co.uk
Fri Oct 22 06:00:58 EDT 1999


Sorry if this article is a little long - it contains about 90 lines of source.

In article <380F22AA.5EF2D410 at RUS.Uni-Stuttgart.De>, Andreas Rozek 
<Andreas.Rozek at RUS.Uni-Stuttgart.De> wrote:
>is there any possibility to launch an external program (without repla-
>cing the current process - i.e., no os.exec* call alone) and catch the
>stdin, stderr and stdout IO streams (the latter also renders spawn and
>fork together with exec* unusable)?

On UNIX, you do need to go with fork/exec, also using pipes to set up the 
output streams of the child process. The commands module in the Python 
standard library does this for you.

>
>A platform-independent solution  would be preferrable - however,  it's
>more important for me to get it for Windows 98!

Sadly, fork/exec doesn't work on any of the Windows platforms. Happily, Mark 
Hammond's fantastic Pythonwin package provides access to the Win32 
CreateProcess API together with the pipe handling APIs you'll need. You can 
get Pythonwin from Mark's Python Starship page at 
http://starship.python.net/crew/mhammond/

If the child process you want to run is a console application or a .bat file, 
you'll need a further step to stop a DOS console popping up and to correctly 
detect termination of the child process. Microsoft have documented this and 
provided sample ('C') code in various knowledge base articles:

Redirection Issues on Windows 95 MS-DOS Applications
Last reviewed: April 10, 1997
Article ID: Q150956 

Spawn Console Processes with Redirected Standard Handles
Last reviewed: September 9, 1998
Article ID: Q190351 

Article Q150956 gives sample source code for a program called conspawn which 
will run the target program and pass output back to you. You'll need to check 
that it does what you want with command line arguments and all the I/O streams 
you care about.

Here's the code we use after we found all this out.We use it on NT 4.x, Win95 
and Win98. YMMV. You'll probably want a very recent version of Pythonwin.

Cheers,
        Pat
Pat Knight, http://www.wideawake.co.uk

import win32pipe
import pywintypes
import win32process
import win32con
import win32api
import win32file
import string
import types

ver = win32api.GetVersionEx()[3]        # distinguish NT and Win95/98
if ver == 2:                    # we're on NT
    PROCESS_RUNNER = 'cmd.exe /c'
else:
    PROCESS_RUNNER = '<name of program based on MS conspawn.exe>'


def mkarg(arg):
    result = string.replace(arg,'"','"""')
    if string.count(arg, ' ') > 0:
        return ' "' + result + '"'
    else:
        return ' ' + result

def mkcommand(command, args):
    # On 95 waspawn.exe is used to run child processes, it prevents a bug
    # when running dos apps/batch files, a console would otherwise be
    # created to run the batch file in, and this would hang around
    # afterwards leaving WA waiting for it. Also stops a console from
    # popping up.
    # On NT cmd.exe is used since otherwise we couldn't invoke DOS
    # commands. This also prevents console windows from popping up.

    our_command = PROCESS_RUNNER + mkarg(command)
    if type(args) == types.StringType:
        our_command = our_command + mkarg(args)
    else:
        for arg in args:
            our_command = our_command + mkarg(arg)
    return our_command

def alt_popen(command_line):
    start_up_info = win32process.STARTUPINFO()
    security_attributes = pywintypes.SECURITY_ATTRIBUTES()
    security_attributes.bInheritHandle = 1

    # create pipe for stdout of the child
    Pyh_read_end, Pyh_write_end = win32pipe.CreatePipe(security_attributes, 0)

    # Make our end of the pipe uninheritable by duplicating it
    # with inheritance set to FALSE
    Pyh_read_end2 = win32api.DuplicateHandle(
        win32api.GetCurrentProcess(), Pyh_read_end,
        win32api.GetCurrentProcess(), 0, 0, win32con.DUPLICATE_SAME_ACCESS)
    win32file.CloseHandle(Pyh_read_end)

    # duplicate the file handle to act as the standard error of the child.
    Pyh_write_end2 = win32api.DuplicateHandle(
        win32api.GetCurrentProcess(), Pyh_write_end,
        win32api.GetCurrentProcess(), 0, 0, win32con.DUPLICATE_SAME_ACCESS)

    # Create a pipe to be used for the childs stdin
    Pyh_read_end_for_stdin, Pyh_write_end_for_stdin = win32pipe.CreatePipe(
        security_attributes, 0)

    # Make our end of the pipe uninheritable by duplicating it
    # with inheritance set to FALSE
    Pyh_write_end_for_stdin2 = win32api.DuplicateHandle(
        win32api.GetCurrentProcess(), 
        Pyh_write_end_for_stdin,
        win32api.GetCurrentProcess(), 
        0, 0, win32con.DUPLICATE_SAME_ACCESS)
    win32file.CloseHandle(Pyh_write_end_for_stdin)

    # set the STARTUPINFO fields
    start_up_info.dwFlags = 1 |256 #win32process.STARTF_USESTDHANDLES
    start_up_info.wShowWindow = 0; # SW_HIDE

    start_up_info.hStdOutput = Pyh_write_end
    start_up_info.hStdError = Pyh_write_end2
    start_up_info.hStdInput = Pyh_read_end_for_stdin

    # create the process
    process_info = win32process.CreateProcess(None, command_line, 
                                              None, None, 1, 0, None, 
                                              None, start_up_info)  

    win32file.CloseHandle(Pyh_write_end2)
    win32file.CloseHandle(Pyh_read_end_for_stdin)

    return Pyh_read_end2, process_info[0]








More information about the Python-list mailing list