非常に興味深い質問で、簡単に解決できることに驚いています :-)
編集 - Aaciniがコメントで指摘したように、私の最初の答えは質問に完全には答えませんでした。一番下には、質問に直接答えるバージョンがあります。また、元の回答を更新して、発見したいくつかの制限を追加しました
返されるすべての変数の名前の先頭に定数の接頭辞を付けるように規定すると、任意の数の変数を非常に簡単に返すことができます。戻り変数のプレフィックスは、パラメーターの 1 つとして渡すことができます。
必要なのは次の行だけです。
for /f "delims=" %%A in ('set prefix.') do endlocal & set "%%A"
コマンドの結果全体は、set prefix
反復が行われる前にバッファリングされます。最初の反復では、CALL の前に存在していた環境状態に戻すために必要な唯一の ENDLOCAL を実行します。CALL された関数内の ENDLOCAL は、CALL 内で発行された SETLOCAL に対してのみ機能するため、後続の ENDLOCAL 反復は害を及ぼしません。追加の冗長な 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 ステートメントを少し変更して、先頭のドットを削除しました。返される変数の名前をドットで始めることはできないという制限があります。
このバージョンには、遅延展開が有効になっている間に CALL を許可するセーフ リターン テクニックが含まれています。
@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