2

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.

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
szr
  • 139
  • 10

3 Answers3

2

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

Community
  • 1
  • 1
David Ruhmann
  • 11,064
  • 4
  • 37
  • 47
  • Thanks. This does work, though it does fail if the variable's contents contains an ampersand, where I see the output of the `SET` command dumped instead. – szr Feb 12 '14 at 20:33
  • @szr Thanks for bringing that to my attention. The ampersand issue should be fixed now along with a few other special characters. – David Ruhmann Feb 12 '14 at 22:16
  • Thanks, just about there. Works fine with a `SET "a=I like M&Ms"`, though I still get a SET-dump with a `SET "a=I like M^&Ms"`, where an & is escaped with a caret. – szr Feb 12 '14 at 22:33
  • @szr Added support for the `^` character. – David Ruhmann Feb 12 '14 at 22:42
  • Thanks, that appears to have done it. Thank you very much. (I wanted to up vote your post but I don't have enough reputation points yet.) – szr Feb 12 '14 at 23:03
  • 1
    Looks good. Also, changing the end of the loop to [this](http://paste.debian.net/hidden/0e54d44b/) will allow for variables that have undefined/empty values to unset the upstream counter part, making for a more true export. :) Thank again. – szr Feb 14 '14 at 03:45
  • @szr Good point. I updated the post to allow for empty variables to be returned. – David Ruhmann Feb 14 '14 at 14:59
1
:: "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
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • Thanks, though what I was after is a way to do this without having to hard code the variable in the FOR loop, but rather use a variable that contains the needed information. – szr Feb 12 '14 at 20:36
0

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)

Magoo
  • 77,302
  • 8
  • 62
  • 84
  • 1
    One item to note is that `call set` has a big WARNING and performance hit in that the `call` command will parse the working directory and the entire `PATH` variable searching each directory for a `set` `PATHEXT` before calling the `set` command. Also if there are any `set` found then they will instead be called rather than the actual `set` command. – David Ruhmann Feb 13 '14 at 01:13