1

I am looking for a way to get a value return from a start-command launched batch script. Let me explain:

I need to take advantage of multiprocessing by launching multiple sub-batch scripts simultaneously from a main batch script, then retrieve every sub batch file return value when they're done.

I've been using return variables with the call-command as very well explained by dbenham. That solution does not allow multithreading, since sub-batch scripts are run one after the other.

Using the start-command allows multiple running batch scripts, but values are not returned to my main script because apparently the start-command creates a whole new variable context.

Does anybody have a solution/workaround to return values from the sub-scripts to the main script ?

Below is a model of what I need:

mainScript.bat

@echo off
setlocal

set "retval1=0"
set "retval2=0"
REM run two scripts in parallel:
start "" subscript1.bat arg1 retval1
start "" subscript2.bat arg1 retval2

REM wait for returned value
:waiting
call :sleep 1
set /a "DONE=1"
if %retval1% equ 0 set "DONE=0"
if %retval2% equ 0 set "DONE=0"
if %DONE% equ 0 goto :waiting

echo returned values are %retval1% %retval2%
exit /b

subscript1.bat

@echo off
setlocal
set "arg1=%~1"
set "retval1=%~1"

REM do some stuff...

REM return value
(endlocal
  set "%retval1%=%foo%"
)
exit /b
Community
  • 1
  • 1
djoffe
  • 102
  • 8

2 Answers2

2

Can't see any alternative to writing your return values to files, so

main

@ECHO OFF
SETLOCAL
for %%a in (1 2) do (
 del "%temp%\retval%%a" 2>nul
)
start /min "" q225220791.bat arg1 retval1
choice /t 1 /d y >nul
start /min "" q225220791.bat arg1 retval2

:waiting
choice /t 1 /d y >nul
ECHO wait...%retval1%...%retval2%
if not exist "%temp%\retval1" goto waiting
if not exist "%temp%\retval2" goto waiting
 for %%a in (1 2) do (
 for /f "usebackqdelims=" %%i in ("%temp%\retval%%a") do set "retval%%a=%%i"
)

for %%a in (1 2) do (
 del "%temp%\retval%%a" 2>nul
)

echo returned values are %retval1% %retval2%

GOTO :EOF

q225220791.bat

@ECHO OFF
SETLOCAL
:: wait a random time 2..10 sec.

SET /a timeout=%RANDOM% %% 8 + 2
choice /t %timeout% /d y >nul

:: return a random result 12..20

SET /a foo=%RANDOM% %% 8 + 12
>"%temp%\%2" echo %foo%
ENDLOCAL
exit

Relying on the value of the second parameter to the sub-process to set the tempfile name. I've changed the names of the batches to suit my system.

Magoo
  • 77,302
  • 8
  • 62
  • 84
  • +1, I was in the midst of writing the same answer :) Except I would use SET /P to read the temp file instead of FOR /F. – dbenham Mar 20 '14 at 03:34
  • (Sorry I'm too late to edit my first comment) Many thanks to both of you, both FOR /F and SET /P work perfectly! Out of curiosity, what difference is there between these ? As far as I understand, both yield the same result, even for passing and parsing multiple variables. (on one line for FOR, one variable per line for SET) – djoffe Mar 20 '14 at 23:39
  • The `for/f` approach is more general; it's more verbose but allows processing of multiple input lines. `set/p` is more compact, but has to be coaxed to allow more than one one to be input. In the current circumstance, there's no practical difference since you're only procesing a single line. Tomato/tomato. A matter of choice or style. – Magoo Mar 21 '14 at 00:04
0

Not sure if this is even practical, but just testing to avoid the temporary file. So looking for a place in child process that can be readed from parent process i decided to use the window title.

task.cmd

@echo off
    setlocal

rem Retrieve task information and set window title    
    set "taskID=%~1"
    title [task];%taskID%;working;

rem Retrieve the rest of parameters. For this sample, a random value
    set /a "timeToWait=%~2 %% 30"

rem Simulate command processing
    timeout /t %timeToWait%

rem Calculate a return value for this task
    for /f "tokens=1-10 delims=,.:/ " %%a in ("%date%%time%_%~2") do set "returnValue=%%a%%b%%c%%d%%e%%f%%g%%h%%i%%j"

rem Signal the end of the task
    title [task];%taskID%;ended; my return value is %returnValue% ;

rem Wait for master to end this tasks
    cls
    echo Waiting for master....
    waitfor %taskID%

rem Cleanup    
    endlocal

master.cmd

@echo off

    setlocal enableextensions enabledelayedexpansion

rem Configure tasks
    set "taskPrefix=myTSK"
    set "numTasks=5"

rem Start tasks
    for /l %%a in (1 1 %numTasks%) do (
        set "return[%taskPrefix%%%a]=unknown"
        start "[task];%taskPrefix%%%a;working;" cmd /c "task.cmd %taskPrefix%%%a !random!"
    )

rem Wait for tasks to start    
    timeout /t 2 > nul

rem Wait for tasks to end. Get the list of cmd.exe windows with window title 
rem to see the state of the task

rem Tasks in working state indicate master needs to keep working
rem Tasks in ended state have the return value in the window title and are 
rem waiting for the master to retrieve the value and end them

:wait
    set "keepWaiting="

    for /f "tokens=9 delims=," %%a in ('tasklist /fi "imagename eq cmd.exe" /fo csv /v ^| findstr /l /c:"[task];%taskPrefix%"'
    ) do for /f "tokens=2-4 delims=;" %%b in ("%%a") do (
        if "%%c"=="working" (
            set "keepWaiting=1"
        ) else if "%%c"=="ended" (
            set "return[%%b]=%%d"
            start "" /min waitfor.exe /si %%b
        )
    )

rem If any task has been found in working state, keep waiting
    if defined keepWaiting (
        echo %time% : waiting ...
        timeout /t 5 > nul 
        goto wait
    )

rem All tasks have ended. Show return values
    for /l %%a in (1 1 %numTasks%) do (
        echo task #%%a ended with exit value :[!return[%taskPrefix%%%a]!]
    )

    endlocal

In this sample, the task waits for master using waitfor command. In XP this is not available, and can be replaced with just a pause, and from master.cmd the waiting loop must be modified to include the processID token in the tasklist processing, so the waiting task can be closed with taskkill.

MC ND
  • 69,615
  • 8
  • 84
  • 126