Here is a fully developed RETURN function that can safely return any value across the ENDLOCAL barrier, regardless whether the parent context has delayed expansion enabled or disabled.
I want the function to be able to return to a command line context, so I cannot use
IF "!!" EQU "" to test for delayed expansion. Instead I rely on the fact that COMSPEC should always be defined, so I can use
IF "!COMSPEC!" EQU "%COMSPEC%".
EDIT - I've adopted jeb's brilliant idea to use IF "^!^" equ "^!" instead.
2017-04-29 EDIT - v2.1 Bug fix for when return value should be undefined. Thanks to random for reporting the bug2017-08-21 EDIT - v3.0 Added support for return of multiple variablesThe RETURN.BAT code is designed to be a stand-alone utility that can be placed in a folder referenced by PATH so it can easily be called by any script. But instructions are included within the code on how to embed the function directly within your own script.
RETURN.BAT Version 3.0Code: Select all
::RETURN.BAT Version 3.0
@if "%~2" equ "" (goto :return.special) else goto :return
:::
:::call RETURN ValueVar ReturnVar [ErrorCode]
::: Used by batch functions to EXIT /B and safely return any value across the
::: ENDLOCAL barrier.
::: ValueVar = The name of the local variable containing the return value.
::: ReturnVar = The name of the variable to receive the return value.
::: ErrorCode = The returned ERRORLEVEL, defaults to 0 if not specified.
:::
:::call RETURN "ValueVar1 ValueVar2 ..." "ReturnVar1 ReturnVar2 ..." [ErrorCode]
::: Same as before, except the first and second arugments are quoted and
::: space delimited lists of variable names.
:::
::: Note that the total length of all assignments (variable names and values)
::: must be less then 3.8k bytes. No checks are performed to verify that all
::: assignments fit within the limit. Variable names must not contain space,
::: tab, comma, semicolon, caret, asterisk, question mark, or exclamation point.
:::
:::call RETURN init
::: Defines return.LF and return.CR variables. Not required, but should be
::: called once at the top of your script to improve performance of RETURN.
:::
:::return /?
::: Displays this help
:::
:::return /V
::: Displays the version of RETURN.BAT
:::
:::
:::RETURN.BAT was written by Dave Benham and DosTips user jeb, and was originally
:::posted within the folloing DosTips thread:
::: http://www.dostips.com/forum/viewtopic.php?f=3&t=6496
:::
::==============================================================================
:: If the code below is copied within a script, then the :return.special code
:: can be removed, and your script can use the following calls:
::
:: call :return ValueVar ReturnVar [ErrorCode]
::
:: call :return.init
::
:return ValueVar ReturnVar [ErrorCode]
:: Safely returns any value(s) across the ENDLOCAL barrier. Default ErrorCode is 0
setlocal enableDelayedExpansion
if not defined return.LF call :return.init
if not defined return.CR call :return.init
set "return.normalCmd="
set "return.delayedCmd="
set "return.vars=%~2"
for %%a in (%~1) do for /f "tokens=1*" %%b in ("!return.vars!") do (
set "return.normal=!%%a!"
if defined return.normal (
set "return.normal=!return.normal:%%=%%3!"
set "return.normal=!return.normal:"=%%4!"
for %%C in ("!return.LF!") do set "return.normal=!return.normal:%%~C=%%~1!"
for %%C in ("!return.CR!") do set "return.normal=!return.normal:%%~C=%%2!"
set "return.delayed=!return.normal:^=^^^^!"
) else set "return.delayed="
if defined return.delayed call :return.setDelayed
set "return.normalCmd=!return.normalCmd!&set "%%b=!return.normal!"^!"
set "return.delayedCmd=!return.delayedCmd!&set "%%b=!return.delayed!"^!"
set "return.vars=%%c"
)
set "err=%~3"
if not defined err set "err=0"
for %%1 in ("!return.LF!") do for /f "tokens=1-3" %%2 in (^"!return.CR! %% "") do (
(goto) 2>nul
(goto) 2>nul
if "^!^" equ "^!" (%return.delayedCmd:~1%) else %return.normalCmd:~1%
if %err% equ 0 (call ) else if %err% equ 1 (call) else cmd /c exit %err%
)
:return.setDelayed
set "return.delayed=%return.delayed:!=^^^!%" !
exit /b
:return.special
@if /i "%~1" equ "init" goto return.init
@if "%~1" equ "/?" (
for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
exit /b 0
)
@if /i "%~1" equ "/V" (
for /f "tokens=* delims=:" %%A in ('findstr /rc:"^::RETURN.BAT Version" "%~f0"') do @echo %%A
exit /b 0
)
@>&2 echo ERROR: Invalid call to RETURN.BAT
@exit /b 1
:return.init - Initializes the return.LF and return.CR variables
set ^"return.LF=^
^" The empty line above is critical - DO NOT REMOVE
for /f %%C in ('copy /z "%~f0" nul') do set "return.CR=%%C"
exit /b 0
Test script:
Code: Select all
@echo off
setlocal disableDelayedExpansion
call return init
for /f %%C in ('copy /z "%~f0" nul') do set "\r=%%C"
set ^"\n=^
^"
set test1="This & that" ^& the other thing
set test2="This & that" ^& the other thing!
set test3="&<>|%%^" ^&^<^>^|%%^^
set test4="&<>|%%^!" ^&^<^>^|%%^^!
setlocal enableDelayedExpansion
set "test5=!\n!Line One!\n!hidden!\r!Line Two"
set "test6="
set "err1=0"
set "err2=1"
set "err3=2"
set "err4="
set "err5="
set "err6=1"
set "normal=preExistingValue"
set "delayed=preExistingValue"
setlocal disableDelayedExpansion
echo Delayed expansion is DISABLED
:loop
echo ------------------------------------------
for /l %%N in (1 1 6) do (
set "result1="
set "result2="
call :test test%%N result err%%N
call set "err=%%errorlevel%%"
setlocal enableDelayedExpansion
echo result=!result!
echo errorlevel=!err!
endlocal
)
for /l %%N in (1 1 6) do set "result%%N="
echo(
echo Multiple values test:
call :test2
set "err=%errorlevel%"
setlocal enableDelayedExpansion
for /l %%N in (1 1 6) do (
echo test%%N=!test%%N!
echo result%%N=!result%%N!
)
echo errorlevel=%err%
endlocal
if "!!" equ "" exit /b
echo(
echo(
setlocal enableDelayedExpansion
echo Delayed expansion is ENABLED
goto :loop
:test
setlocal enableDelayedExpansion
set rtn=!%1!
echo(
echo %1=!rtn!
call return "rtn" "%2" !%3!
exit /b
:test2
setlocal enableDelayedExpansion
for /l %%N in (1 1 6) do set "rtn%%N=!test%%N!"
call return "rtn1 rtn2 rtn3 rtn4 rtn5 rtn6" "result1 result2 result3 result4 result5 result6" -99
exit /b
--OUTPUT--
Code: Select all
Delayed expansion is DISABLED
------------------------------------------
test1="This & that" & the other thing
result="This & that" & the other thing
errorlevel=0
test2="This & that" & the other thing!
result="This & that" & the other thing!
errorlevel=1
test3="&<>|%^" &<>|%^
result="&<>|%^" &<>|%^
errorlevel=2
test4="&<>|%^!" &<>|%^!
result="&<>|%^!" &<>|%^!
errorlevel=0
test5=
Line One
Line Two
result=
Line One
Line Two
errorlevel=0
test6=
result=
errorlevel=1
Multiple values test:
test1="This & that" & the other thing
result1="This & that" & the other thing
test2="This & that" & the other thing!
result2="This & that" & the other thing!
test3="&<>|%^" &<>|%^
result3="&<>|%^" &<>|%^
test4="&<>|%^!" &<>|%^!
result4="&<>|%^!" &<>|%^!
test5=
Line One
Line Two
result5=
Line One
Line Two
test6=
result6=
errorlevel=-99
Delayed expansion is ENABLED
------------------------------------------
test1="This & that" & the other thing
result="This & that" & the other thing
errorlevel=0
test2="This & that" & the other thing!
result="This & that" & the other thing!
errorlevel=1
test3="&<>|%^" &<>|%^
result="&<>|%^" &<>|%^
errorlevel=2
test4="&<>|%^!" &<>|%^!
result="&<>|%^!" &<>|%^!
errorlevel=0
test5=
Line One
Line Two
result=
Line One
Line Two
errorlevel=0
test6=
result=
errorlevel=1
Multiple values test:
test1="This & that" & the other thing
result1="This & that" & the other thing
test2="This & that" & the other thing!
result2="This & that" & the other thing!
test3="&<>|%^" &<>|%^
result3="&<>|%^" &<>|%^
test4="&<>|%^!" &<>|%^!
result4="&<>|%^!" &<>|%^!
test5=
Line One
Line Two
result5=
Line One
Line Two
test6=
result6=
errorlevel=-99
And here is a script to partially test returning to a command line context:
testReturn.batCode: Select all
@echo off
setlocal disableDelayedExpansion
set ^"original=^
Hello world!^
"&<>|%%^!" ^&^<^>^|%%^^!^"
set original
call return original %1 0
Test results:
Code: Select all
C:\test>testReturn disabledResult
original=
Hello world!
"&<>|%^!" &<>|%^!
C:\test>set disabledResult
disabledResult=
Hello world!
"&<>|%^!" &<>|%^!
C:\test>cmd /v:on
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
C:\test>testReturn enabledResult
original=
Hello world!
"&<>|%^!" &<>|%^!
C:\test>set enabledResult
enabledResult=
Hello world!
"&<>|%^!" &<>|%^!
Dave Benham