6

I m trying to execute several batch-scripts in a python loop. However the said bat-scripts contain cmd /K and thus do not "terminate" (for lack of a better word). Therefore python calls the first script and waits forever...

Here is a pseudo-code that gives an idea of what I am trying to do:

import subprocess

params = [MYSCRIPT, os.curdir]    
for level in range(10):
    subprocess.call(params)  

My question is: "Is there a pythonic solution to get the console command back and resume looping?"


EDIT: I am now aware that it is possible to launch child processes and continue without waiting for them to return, using

Popen(params,shell=False,stdin=None,stdout=None,stderr=None,close_fds=True)

However this would launch my entire loop almost simultaneously. Is there a way to wait for the child process to execute its task and return when it hits the cmd /K and becomes idle.

snake_charmer
  • 2,845
  • 4
  • 26
  • 39
  • OBS: before someone asks why I do not remove the `cmd /K` from the script, I have several reasons for it. The easiest to explain is that those scripts are shared with my colleagues and I do not want to temper with them. – snake_charmer Dec 07 '16 at 14:59
  • 2
    Looks like [this answer](http://stackoverflow.com/a/6700359/174652) may work for you. – joebeeson Dec 07 '16 at 15:29
  • Thanks Joeb! it seems that `DETACHED_PROCESS` skips the execution of the batch altogether. The other options suggested for `Popen`, i.e. `shell=True, stdin=None, stdout=None, stderr=None, close_fds=True` are very useful. The script does not wait for the child process to terminate. This will be very useful when I want to run the 10 scripts as parallel threads. However I think I am looking for a way to wait for the child process to execute its task and return when it hits the `cmd /K` and becomes idle. Is this possible at all? should I edit my question to reflect this? – snake_charmer Dec 08 '16 at 09:29

2 Answers2

3

There is no built in way, but it's something you can implement.

Examples are with bash since I don't have access to a Windows machine, but should be similar for cmd \K

It might be as easy as:

import subprocess

# start the process in the background
process = subprocess.Popen(
    ['bash', '-i'],
    stdout=subprocess.PIPE,
    stdin=subprocess.PIPE
)

# will throw IO error if process terminates by this time for some reason
process.stdin.write("exit\n")
process.wait()

This will send an exit command to the shell, which should be processed just as the script terminates causing it to exit ( effectively canceling out the \K )

Here's a more elaborate answer in case you need a solution that checks for some output:

import subprocess

# start the process in the background
process = subprocess.Popen(
    ['bash', '-i'],
    stdout=subprocess.PIPE,
    stdin=subprocess.PIPE
)

    # Wait for the process to terminate
    process.poll()
    while process.returncode is None:
        # read the output from the process
        # note that can't use readlines() here as it would block waiting for the process
        lines = [ x for x in process.stdout.read(5).split("\n") if x]
        if lines:
            # if you want the output to show, you'll have to print it yourself
            print(lines )
            # check for some condition in the output
            if any((":" in x for x in lines)):
                # terminate the process
                process.kill()
                # alternatively could send it some input to have it terminate
                # process.stdin.write("exit\n")
        # Check for new return code
        process.poll()

The complication here is with reading the output, as if you try to read more than is available, the process will block.

Alpar
  • 2,807
  • 23
  • 16
  • Brilliant! This is just what was needed. I won't need any output for, but I appreciate your thoroughness, hope it comes handy to someone with a similar issue. – snake_charmer Dec 16 '16 at 11:04
2

Here is something I use where I start a bunch of processes (2 in this example) and wait for them at the end before the program terminates. It can be modified to wait for specific processes at different times (see comments). In this example one process prints out the %path% and the other prints the directory contents.

import win32api, win32con, win32process, win32event

def CreateMyProcess2(cmd):
    ''' create process width no window that runs sdelete with a bunch of arguments'''
    si         = win32process.STARTUPINFO()
    info = win32process.CreateProcess(
        None,      # AppName
        cmd,       # Command line
        None,      # Process Security
        None,      # Thread Security
        0,         # inherit Handles?
        win32process.NORMAL_PRIORITY_CLASS,
        None,      # New environment
        None,      # Current directory
        si)        # startup info
    return info[0]
# info is tuple (hProcess, hThread, processId, threadId)

if __name__ == '__main__' :
    handles       = []

    cmd = 'cmd /c "dir/w"'
    handle = CreateMyProcess2(cmd)
    handles.append(handle)

    cmd = 'cmd /c "path"'
    handle = CreateMyProcess2(cmd)
    handles.append(handle)

    rc = win32event.WaitForMultipleObjects(
        handles, # sequence of objects (here = handles) to wait for
        1,       # wait for them all (use 0 to wait for just one)
        15000)   # timeout in milli-seconds
    print rc
    # rc = 0   if all tasks have completed before the time out  

Approximate Output (edited for clarity):

PATH=C:\Users\Philip\algs4\java\bin;C:\Users\Philip\bin;C:\Users\Philip\mksnt\ etc......

Volume in drive C has no label. Volume Serial Number is 4CA0-FEAD
Directory of C:\Users\Philip\AppData\Local\Temp
[.]
[..]
FXSAPIDebugLogFile.txt
etc....
1 File(s) 0 bytes
3 Dir(s) 305,473,040,384 bytes free

0 <-- value of "rc"

Marichyasana
  • 2,966
  • 1
  • 19
  • 20
  • Interesting answer, even though it has the obvious limitation that the user should estimate runtime a priori. I have tried a slightly different approach, running one single handle at the time `handle = CreateMyProcess2( cmd ) ; rc = win32event.WaitForSingleObject(handle, 30000)` but could not arrange my `subprocess.call` arguments into a `cmd` string that Windows would understand. Is there a way to create a `subprocess` object without calling it and then convert it into a handle? – snake_charmer Dec 16 '16 at 11:37
  • You can use 2147483647. – Marichyasana Dec 16 '16 at 19:46
  • what do you mean? what is 2147483647? – snake_charmer Dec 16 '16 at 20:33
  • 2147483647. The largest positive value of a signed integer on a 32 bit operating system. That's a lot of time. – Marichyasana Dec 17 '16 at 21:16