非常有趣的问题,我很惊讶它是多么容易解决:-)
编辑 - 正如 Aacini 在他的评论中指出的那样,我的原始答案并没有完全回答这个问题。在底部,我有一个可以直接回答问题的版本。我还更新了我的原始答案,以包含我发现的更多限制
如果您规定要返回的所有变量的名称都以常量前缀开头,则可以非常轻松地返回任意数量的变量。返回变量前缀可以作为参数之一传入。
只需以下行:
for /f "delims=" %%A in ('set prefix.') do endlocal & set "%%A"
set prefix
在任何迭代发生之前,命令的全部结果都会被缓冲。第一次迭代执行返回到 CALL 之前存在的环境状态所需的唯一 ENDLOCAL。随后的 ENDLOCAL 迭代不会造成任何损害,因为 CALLed 函数中的 ENDLOCAL 仅适用于在 CALL 中发出的 SETLOCAL。额外的冗余 ENDLOCAL 将被忽略。
这个非常简单的解决方案有一些非常好的功能:
- 理论上返回的变量数量没有限制。
- 返回的值可以包含几乎任何字符组合。
- 返回值可以接近 8191 字节的理论最大长度。
还有一些限制:
- 返回值不能包含换行符
- 如果返回值的最后一个字符是回车,那么最后的回车将被删除。
- 如果在进行 CALL 时启用了延迟扩展,则包含的任何返回值都
!
将被破坏。
- 我还没有想出一个优雅的方法来将返回的变量设置为未定义。
这是一个返回可变数量值的可变参数函数的简单示例
@echo off
setlocal
set varBeforeCall=ok
echo(
call :variadic callA 10 -34 26
set callA
set varBeforeCall
echo(
call :variadic callB 1 2 5 10 50 100
set callB
set varBeforeCall
exit /b
:variadic returnPrefix arg1 [arg2 ...]
@echo off
setlocal enableDelayedExpansion
set /a const=!random!%%100
:: Clear any existing returnPrefix variables
for /f "delims==" %%A in ('set %1. 2^>nul') do set "%%A="
:: Define the variables to be returned
set "%~1.cnt=0"
:argLoop
if "%~2" neq "" (
set /a "%~1.cnt+=1"
set /a "%~1.!%~1.cnt!=%2*const"
shift /2
goto :argLoop
)
:: Return the variables accross the ENDLOCAL barrier
for /f "delims=" %%A in ('set %1. 2^>nul') do endlocal & set "%%A"
exit /b
这是一个示例运行结果:
callA.1=40
callA.2=-136
callA.3=104
callA.cnt=3
varBeforeCall=ok
callB.1=24
callB.2=48
callB.3=120
callB.4=240
callB.5=1200
callB.6=2400
callB.cnt=6
varBeforeCall=ok
这是启用延迟扩展时可以安全调用的版本
通过一些额外的代码,可以消除在启用延迟扩展并且返回值包含时调用函数的限制!
。
返回值会根据需要进行操作,以!
在启用延迟扩展时进行保护。代码经过优化,使得相对昂贵的微型计算(特别是 CALL)仅在启用延迟扩展且值包含时才执行!
。
返回的值仍然不能包含换行符。!
一个新的限制是,如果返回值包含并且在进行 CALL 时启用了延迟扩展,则所有回车将被删除。
这是一个演示。
@echo off
setlocal
set varBeforeCall=ok
echo(
echo Results when delayed expansion is Disabled
call :variadic callA 10 -34 26
set callA
set varBeforeCall
setlocal enableDelayedExpansion
echo(
echo Results when delayed expansion is Enabled
call :variadic callB 1 2 5 10 50 100
set callB
set varBeforeCall
exit /b
:variadic returnPrefix arg1 [arg2 ...]
@echo off
:: Determine if caller has delayed expansion enabled
setlocal
set "NotDelayed=!"
setlocal enableDelayedExpansion
set /a const=!random!%%100
:: Clear any existing returnPrefix variables
for /f "delims==" %%A in ('set %1. 2^>nul') do set "%%A="
:: Define the variables to be returned
set "%~1.cnt=0"
:argLoop
if "%~2" neq "" (
set /a "%~1.cnt+=1"
set /a "%~1.!%~1.cnt!=%2*const"
shift /2
goto :argLoop
)
set %~1.trouble1="!const!\^^&^!%%"\^^^^^&^^!%%
set %~1.trouble2="!const!\^^&%%"\^^^^^&%%
:: Prepare variables for return when caller has delayed expansion enabled
if not defined NotDelayed for /f "delims==" %%A in ('set %1. 2^>nul') do (
for /f delims^=^ eol^= %%V in ("!%%A!") do if "%%V" neq "!%%A!" (
set "%%A=!%%A:\=\s!"
set "%%A=!%%A:%%=\p!"
set "%%A=!%%A:"=\q!"
set "%%A=!%%A:^=\c!"
call set "%%A=%%%%A:^!=^^^!%%" ^^!
set "%%A=!%%A:^^=^!"
set "%%A=!%%A:\c=^^!"
set "%%A=!%%A:\q="!"
set "%%A=!%%A:\p=%%!"
set "%%A=!%%A:\s=\!"
)
)
:: Return the variables accross the ENDLOCAL barrier
for /f "delims=" %%A in ('set %1. 2^>nul') do endlocal & endlocal & set "%%A"
exit /b
以及一些示例结果:
Results when delayed expansion is Disabled
Environment variable callA not defined
callA.1=780
callA.2=-2652
callA.3=2028
callA.cnt=3
callA.trouble1="78\^&!%"\^&!%
callA.trouble2="78\^&%"\^&%
varBeforeCall=ok
Results when delayed expansion is Enabled
Environment variable callB not defined
callB.1=48
callB.2=96
callB.3=240
callB.4=480
callB.5=2400
callB.6=4800
callB.cnt=6
callB.trouble1="48\^&!%"\^&!%
callB.trouble2="48\^&%"\^&%
varBeforeCall=ok
请注意,无论在进行 CALL 时是否启用了延迟扩展,返回的故障值的格式都是一致的。如果不是因为!
.
编辑:这是一个直接回答问题的版本
原始问题规定每个返回变量的名称都应该在参数列表中提供。我修改了我的算法,在函数中为每个变量名加上一个点。然后我稍微修改了最后的返回 FOR 语句以去除前导点。有一个限制,返回的变量的名称不能以点开头。
此版本包括安全返回技术,允许在启用延迟扩展时调用。
@echo off
setlocal disableDelayedExpansion
echo(
set $A=before
set $varBeforeCall=ok
echo ($) Values before CALL:
set $
echo(
echo ($) Values after CALL when delayed expansion is Disabled:
call :variadic $A $B
set $
setlocal enableDelayedExpansion
echo(
set #A=before
set #varBeforeCall=ok
echo (#) Values before CALL:
set #
echo(
echo (#) Values after CALL when delayed expansion is Enabled:
call :variadic #A #B #C
set #
exit /b
:variadic arg1 [arg2 ...]
@echo off
:: Determine if caller has delayed expansion enabled
setlocal
set "NotDelayed=!"
setlocal enableDelayedExpansion
:: Clear any existing . variables
for /f "delims==" %%A in ('set . 2^>nul') do set "%%A="
:: Define the variables to be returned
:argLoop
if "%~1" neq "" (
set /a num=!random!%%10
set ^".%~1="!num!\^^&^!%%"\^^^^^&^^!%%"
shift /1
goto :argLoop
)
:: Prepare variables for return when caller has delayed expansion enabled
if not defined NotDelayed for /f "delims==" %%A in ('set . 2^>nul') do (
for /f delims^=^ eol^= %%V in ("!%%A!") do if "%%V" neq "!%%A!" (
set "%%A=!%%A:\=\s!"
set "%%A=!%%A:%%=\p!"
set "%%A=!%%A:"=\q!"
set "%%A=!%%A:^=\c!"
call set "%%A=%%%%A:^!=^^^!%%" ^^!
set "%%A=!%%A:^^=^!"
set "%%A=!%%A:\c=^^!"
set "%%A=!%%A:\q="!"
set "%%A=!%%A:\p=%%!"
set "%%A=!%%A:\s=\!"
)
)
:: Return the variables accross the ENDLOCAL barrier
for /f "tokens=* delims=." %%A in ('set . 2^>nul') do endlocal & endlocal & set "%%A"
exit /b
和样本结果:
($) Values before CALL:
$A=before
$varBeforeCall=ok
($) Values after CALL when delayed expansion is Disabled:
$A="5\^&!%"\^&!%
$B="5\^&!%"\^&!%
$varBeforeCall=ok
(#) Values before CALL:
#A=before
#varBeforeCall=ok
(#) Values after CALL when delayed expansion is Enabled:
#A="7\^&!%"\^&!%
#B="2\^&!%"\^&!%
#C="0\^&!%"\^&!%
#varBeforeCall=ok