3

可変数の引数を取る関数があります。各引数は、関数が直接変更する参照です。関数を呼び出す方法は次のとおりです。

set "A=" && set "B=" && set "C="
call :variadic_func A B C
echo [%A%][%B%][%C%]

goto :eof

変数のスコープを制限するために setlocal を使用しない場合、関数は正常に動作します。この関数は、参照 X、Y、および Z を作成し、それらに 1、2、および 3 を割り当てます。関数が戻ると、呼び出し元は変数 A、B、および C が 1、2、および 3 であることを確認します。これが可変引数関数であるふりをして、実行時に引数の数を計算します。

:variadic_func
set "x=%1" && set "y=%2" && set "z=%3"
set "%x%=1" && set "%y%=2" && set "%z%=3"
goto :eof

出力:

C:\scratch\variadic_batch>variadic.bat
[1][2][3]

しかし、関数の変数のスコープをsetlocal. つまり、X、Y、および Z に書き込んだ値はすべて で破棄されますendlocal。関数から値を取得するにはどうすればよいですか?

:variadic_func
setlocal
set "x=%1" && set "y=%2" && set "z=%3"
set "%x%=1" && set "%y%=2" && set "%z%=3"

endlocal && (
    call set "%x%=%%%%x%%%%"
    call set "%y%=%%%%y%%%%"
    call set "%z%=%%%%z%%%%"
)

goto :eof

残念ながら、呼び出しコンテキストは値%x%%y%、およびを受け取ります%z%。上記のコードは次のように展開されると思いました。 1.%x%最初に展開して を取得しますcall set A=%%A%%。次に、呼び出しが実行され、評価されA=%A%ます。しかし、テキスト%A%を評価する代わりに変数 A に代入するだけです。

C:\scratch\variadic_batch>variadic.bat
[%x%][%y%][%z%]

期待どおりに動作しないのはなぜですか?どうすれば修正できますか?

setlocal EnableDelayedExpansion関数呼び出しの前に a を実行することを考えたので、関数で endlocal を実行すると遅延拡張が引き続き利用可能になる可能性がありますが、それが機能する場合でも、関数が呼び出し元に依存していなければいいでしょう遅延拡張ブロックで...遅延拡張ブロックがスタックするかどうかさえわかりません)

4

3 に答える 3

3

これは興味深いトピックです!関数が取得する変数の数が事前にわかっている場合は、最後に適切な行を組み立てて、次のように呼び出し元の環境に値を返すことができます。

:variadic_func
setlocal EnableDelayedExpansion
set "x=%1" & set "y=%2" & set "z=%3"
set "%x%=1" & set "%y%=2" & set "%z%=3"
for /F "tokens=1-3" %%a in ("!%x%! !%y%! !%z%!") do (
   endlocal
   set "%x%=%%a" & set "%y%=%%b" & set "%z%=%%c"
)
exit /B

ただし、変数の数がわからない場合は、以前の方法を使用できません。

(私はexit /Bサブルーチンを終了goto :EOFし、メインファイルのみを使用していました)

とにかくあなたの例は不正確です。なぜなら、いくつの変数が来るかわからない場合、固定名を「x」、「y」、または「z」として使用することはできないからです。この状況を管理する唯一の方法は、名前を配列に格納してから、配列要素を処理することです。

このように、関数が終了する前に、endlocalの後にFORで実行される「var = value」ペアのリストを組み立てることができるため、変数は呼び出し元の環境で定義されます。

@echo off

call :variadic_func One Two Three
echo THREE VARS: One=[%One%] Two=[%Two%] Three=[%Three%] Four=[%Four%] Five=[%Five%]
call :variadic_func One Two Three Four Five
echo FIVE VARS:  One=[%One%] Two=[%Two%] Three=[%Three%] Four=[%Four%] Five=[%Five%]
goto :EOF

:variadic_func
setlocal EnableDelayedExpansion
rem Collect the list of variable names in "var" array:
set i=0
:nextVar
   if "%1" equ "" goto endVars
   set /A i+=1
   set var[%i%]=%1
   shift
goto nextVar
:endVars
rem Assign random numbers to the variables (for example):
for /L %%i in (1,1,%i%) do (
   set !var[%%i]!=!random!
)
rem Assemble the list of "var=value" assignments that will be executed at end:
set assignments=
for /L %%i in (1,1,%i%) do (
   for %%v in (!var[%%i]!) do (
      set assignments=!assignments! "%%v=!%%v!"
   )
)
rem Execute the list of variable assignments in the caller's environment:
endlocal & for %%a in (%assignments%) do set %%a
exit /B

出力:

THREE VARS: One=[29407] Two=[21271] Three=[5873] Four=[] Five=[]
FIVE VARS:  One=[30415] Two=[2595] Three=[22479] Four=[13956] Five=[26412]

編集:

私はdbenhamのソリューションからメソッドを借用して、彼が指摘したものを除いて、制限なしで任意の数の変数を返しました。これは新しいバージョンです:

@echo off

call :variadic_func One Two Three
echo THREE VARS: One=[%One%] Two=[%Two%] Three=[%Three%] Four=[%Four%] Five=[%Five%]
call :variadic_func One Two Three Four Five
echo FIVE VARS:  One=[%One%] Two=[%Two%] Three=[%Three%] Four=[%Four%] Five=[%Five%]
goto :EOF

:variadic_func
setlocal EnableDelayedExpansion
rem Assemble the list of variable names in "var" array:
set i=0
:nextVar
   if "%1" equ "" goto endVars
   set /A i+=1
   set var[%i%]=%1
   shift
goto nextVar
:endVars
rem Assign random numbers to the variables (for example):
for /L %%i in (1,1,%i%) do (
   set !var[%%i]!=!random!
)
rem Complete "var[i]=name" array contents to "var[i]=name=value"
for /L %%i in (1,1,%i%) do (
   for %%v in (!var[%%i]!) do (
      set "var[%%i]=%%v=!%%v!"
   )
)
rem Execute the list of variable assignments in the caller's environment:
for /F "tokens=1* delims==" %%a in ('set var[') do endlocal & set "%%b"
exit /B

アントニオ

于 2013-01-08T22:24:15.527 に答える
3

非常に興味深い質問で、簡単に解決できることに驚いています :-)

編集 - 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
于 2013-01-08T23:34:53.080 に答える
0

値からパーセントのペアを削除します。 call set "%x%=%%%%x%%%%"の中へcall set "%x%=%%%x%%%"

現在、次のように評価しています。

:: Here is the base command
call set "%x%=%%%%x%%%%"
:: The single percents are evaluated and the doubles are escaped.
set "A=%%x%%"
:: The doubles are escaped again leaving literal % signs
"A=%x%"

次のようにします。

:: Here is the base command
call set "%x%=%%%x%%%"
:: The single percents are evaluated and the doubles are escaped.
set "A=%A%"
:: The single percents are evaluated.
"A=1"

コマンドを使用して変数展開を行う場合call、単一のパーセント%が最初に評価され、次に%%バッチ エスケープのために二重のパーセントが評価されます。

バッチ コマンドは左から右に読み取られます。%したがって、 のように記号が偶数個ある場合%%%%、1 番目と 3 番目のパーセント記号は 2 番目と 4 番目のパーセント記号のエスケープ文字として使用され、変数の評価に使用するパーセント記号は残りません。

于 2013-01-08T20:26:13.130 に答える