szr
szr

Reputation: 139

Return Multiple Variables Dynamically from SETLOCAL

I'm trying to figure how to return multiple variables, in a rather dynamic fashion where I just maintain a list of variable names and then do the ENDLOCAL "export" with a loop, but it seems the !%%V! expansion in the inner FOR loop isn't expanding soon enough or so.

Is there something I'm missing or is there another way to accomplish this?

Example code:

SETLOCAL EnableExtensions EnableDelayedExpansion
[...]
SET "RETURN=FOO BAR"
[...]
SET "RETURN=%RETURN% BAZ"
[...]

:END

:: "Export" %FOO%, %BAR%, and %BAZ% to calling context.
ENDLOCAL & (
   FOR %%N IN (%RETURN%) DO (
      FOR /f %%V in ("%%N") DO SET %%N=!%%V!
   )
)

   EXIT /B

Thank you for any help.

Upvotes: 2

Views: 1110

Answers (3)

David Ruhmann
David Ruhmann

Reputation: 11367

Here is a return routine for preserving variables across scope. Just call the return routine with the variables names as parameters and then call endlocal %return%.

Routine:

:return [Variables...]
setlocal enabledelayedexpansion
set "return="
:_return
if "%~1"=="" endlocal & exit /b 1
if not defined %~1 goto __return
set "%~1=!%~1:"=""!"
set "%~1=!%~1:^=^^!"
set "%~1=!%~1:<=^<!"
set "%~1=!%~1:>=^>!"
set "%~1=!%~1:&=^&!"
set "%~1=!%~1:|=^|!"
:__return
set "return=!return!^&set ""%~1=!%~1!"""
if not "%~2"=="" shift & goto _return
setlocal disabledelayedexpansion
set "return=%return:!=^^^^^^^!%"
endlocal & endlocal & set "return=%return:""="%"
exit /b 0

Usage:

call :return foo bar baz
endlocal %return%

Example:

Only variable a will be preserved outside the scope.

@echo off
setlocal
echo Inside
set "a=1"
set "b=2"
echo(%a%
echo(%b%
call :return a
endlocal %return%
echo Outside
echo(%a%
echo(%b%
exit /b 0

:return [Variables...]
setlocal enabledelayedexpansion
set "return="
:_return
if "%~1"=="" endlocal & exit /b 1
if not defined %~1 goto __return
set "%~1=!%~1:"=""!"
set "%~1=!%~1:^=^^!"
set "%~1=!%~1:<=^<!"
set "%~1=!%~1:>=^>!"
set "%~1=!%~1:&=^&!"
set "%~1=!%~1:|=^|!"
:__return
set "return=!return!^&set ""%~1=!%~1!"""
if not "%~2"=="" shift & goto _return
setlocal disabledelayedexpansion
set "return=%return:!=^^^^^^^!%"
endlocal & endlocal & set "return=%return:""="%"
exit /b 0

Problem in your code

As for why your code is not working as you expect, is because the variable must be expanded before the endlocal command is run. Read this excellent SO post about host the command line parses batch scripts: Answer: How does the Windows Command Interpreter (CMD.EXE) parse scripts?

Update

Upvotes: 2

Magoo
Magoo

Reputation: 79983

How about

@ECHO OFF
SETLOCAL
FOR %%i IN (foo bar baz) DO SET $%%i=value:%%i
ECHO == initial values ===
SET $
SET "RETURN=$FOO $BAR"
CALL :demo
ECHO == after CALL modifying %return% ===
SET $

FOR %%i IN (foo bar baz) DO SET $%%i=value:%%i
ECHO == initial values ===
SET $

SET "RETURN=%RETURN% $BAZ"
CALL :demo
ECHO == after CALL modifying %return% ===
SET $

GOTO :eof

:demo
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%i IN (foo bar baz) DO SET $%%i=modified:%%i
SET "$retvals="
FOR %%N IN (%RETURN%) DO SET "$retvals=!$retvals!^&set %%N=%%%%N%%"
CALL SET "$retvals=%$retvals%"

:: "Export" %FOO%, %BAR%, and %BAZ% to calling context.
endlocal%$retvals%

EXIT /B
GOTO :EOF

Yielding

== initial values ===
$bar=value:bar
$baz=value:baz
$foo=value:foo
== after CALL modifying $FOO $BAR ===
$bar=modified:bar
$baz=value:baz
$foo=modified:foo
== initial values ===
$bar=value:bar
$baz=value:baz
$foo=value:foo
== after CALL modifying $FOO $BAR $BAZ ===
$bar=modified:bar
$baz=modified:baz
$foo=modified:foo

(I just changed the varnames for convenience of display using set)

Upvotes: 0

Aacini
Aacini

Reputation: 67216

:: "Export" %FOO%, %BAR%, and %BAZ% to calling context.
for %%a in ("endlocal" "FOO=%FOO%" "BAR=%BAR%" "BAZ=%BAZ%") do (
   if %%a equ "endlocal" (endlocal) else set %%a
)
exit /B

Upvotes: 1

Related Questions