2013-01-08 73 views
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 

輸出:

但我想限制我的函數的變量範圍與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,所以當我在函數中做endlocal時,延遲擴展仍然可用,但即使這樣做可以,如果函數不依賴它在呼叫者在延遲擴展塊......我甚至不知道是否延遲擴展塊堆棧)

回答

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命令變量擴展,單百分比%會首先評估,然後由於批次溢出,雙重百分比%%秒。

批處理命令從左到右讀取。因此,如果有%等偶數%%%%,則第一個和第三個百分號將用作第二個和第四個換碼符號的換碼符號,而不會留下用於變量評估的百分號。

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「。管理這種情況的唯一方法是將名稱存儲在數組中,然後處理數組元素。

這樣,在函數結束之前,我們可以組裝的「VAR = value」對將在一個FOR的ENDLOCAL之後執行的列表,所以變量將在調用者的環境中定義:

@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 

安東尼

+0

尼斯Aacini,但也有一些限制:1)用引號和特殊字符值可能會導致問題。例子'「this&that」&other' 2)具有'*'和'?'的值將被破壞。 – dbenham

+0

另一個限制 - 所有返回的定義的總和長度必須在〜8191字節內。 – dbenham

+0

謝謝Dave,我借用了你的方法並改進了我的解決方案! ;) – Aacini

3

很有趣的問題,我很驚訝它是多麼容易解決:-)

編輯- 我原來的答覆並沒有完全回答這個問題,正如Aacini在他的評論中指出的那樣。最後,我有一個直接回答問題的版本。我還更新了我的原始答案,以包含我發現的更多限制

如果您規定要返回的所有變量的名稱都帶有固定前綴,您可以非常容易地返回任意數量的變量。返回變量前綴可以作爲參數傳入。

下面的行是所有需要:任何迭代發生之前
for /f "delims=" %%A in ('set prefix.') do endlocal & set "%%A"

set prefix命令的全部結果是緩衝的。第一次迭代執行返回到CALL之前存在的環境狀態所需的唯一ENDLOCAL。後續的ENDLOCAL迭代不會造成危害,因爲CALLed函數中的ENDLOCAL只對在CALL中發出的SETLOCAL有效。額外的冗餘ENDLOCAL被忽略。

有這個非常簡單的解決方案的一些非常不錯的功能:

  • 有理論上沒有限制返回的變量的數目。
  • 返回的值可以包含幾乎任何字符的組合。
  • 返回的值可以接近8191字節長的理論最大長度。

也有一些限制:

  • 返回的值不能包含換行符
  • 如果返回值的最後一個字符是一個回車,即最終的回車將被剝離。
  • 如果在進行CALL時啓用延遲擴展,則任何包含!的返回值都將被損壞。
  • 我還沒有想出一個優雅的方法來設置一個返回的變量爲undefined。

這裏是一個返回可變數值

@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 

這裏是一個版本在啓用延遲擴展時可以安全調用

有點分機ra代碼,可以在啓用延遲擴展並且返回值包含!的情況下刪除有關調用該功能的限制。

當啓用延遲擴展時,必要時會處理返回的值以保護!。該代碼已經過優化,因此只有在啓用了延遲擴展並且該值包含!時才執行相對昂貴的小型處理(特別是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 

注意返回的麻煩值的格式是如何一致調用時延遲擴展是否被啓用。如果由於!而導致延遲擴展不適用於額外的代碼,則trouble1值將被損壞。

編輯:這裏是直​​接回答這個問題

原來的問題規定,每個名字返回的變量都應該在參數列表中提供一個版本。我修改了我的算法,在函數內用一個點作爲每個變量名的前綴。然後,我稍微修改了最後返回的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 
+3

+1使用'set'返回值的聰明想法!然而,我認爲這個主題並不是關於返回任何數量變量的方法。如果可能的話,傳遞兩個參數甚至會更容易:要返回的變量的前綴和數量。請注意,這些參數是_not_變量值,但「每個參數都是該函數將直接修改的引用」。據我瞭解,該函數接收幾個_variable names_,並且這些名稱不能被修改,以使該函數更容易編寫。 – Aacini

+0

@Aacini - 我更仔細地閱讀了這個問題,我明白了你的觀點。我提供了另一個直接回答問題的版本。我還發現了一些更多的限制。 – dbenham