我有一个批处理文件,它通过一系列中间变量计算变量:
@echo off
setlocal
set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts
endlocal
%scripts%\activate.bat
最后一行的脚本没有被调用,因为它来自endlocal,它破坏了scripts
环境变量,但它必须在endlocal之后,因为它的目的是设置一堆其他环境变量供用户使用。
如何调用脚本的目的是设置永久环境变量,但是谁的位置是由临时环境变量决定的?
我知道我可以在endlocal之前创建一个临时批处理文件,并在endlocal之后调用它,如果没有别的东西可以解决,我会这样做,但我想知道是否有一个不那么令人讨厌的解决方案。
答案 0 :(得分:22)
ENDLOCAL & SET VAR=%TEMPVAR%
模式是经典的。但有些情况并不理想。
如果您不知道TEMPVAR的内容,那么如果该值包含<
>
&
或|
等特殊字符,则可能会遇到问题。您通常可以使用SET "VAR=%TEMPVAR%"
之类的引号来防止这种情况,但如果存在特殊字符并且该值已被引用,则可能会导致问题。
如果您担心特殊字符,FOR表达式是在ENDLOCAL屏障上传输值的绝佳选择。应在ENDLOCAL之前启用延迟扩展,并在ENDLOCAL之后禁用。
setlocal enableDelayedExpansion
set "TEMPVAR=This & "that ^& the other thing"
for /f "delims=" %%A in (""!TEMPVAR!"") do endlocal & set "VAR=%%~A"
限制:
如果在ENDLOCAL之后启用了延迟扩展,那么如果TEMPVAR包含!
,则最终值将被破坏。
无法传输包含lineFeed字符的值
如果您必须返回多个值,并且您知道某个字符无法显示在任何值中,则只需使用相应的FOR / F选项即可。例如,如果我知道值不能包含|
:
setlocal enableDelayedExpansion
set "temp1=val1"
set "temp2=val2"
for /f "tokens=1,2 delims=|" %%A in (""!temp1!"|"!temp2!"") do (
endLocal
set "var1=%%~A"
set "var2=%%~B"
)
如果必须返回多个值,并且字符集不受限制,则使用嵌套的FOR / F循环:
setlocal enableDelayedExpansion
set "temp1=val1"
set "temp2=val2"
for /f "delims=" %%A in (""!temp1!"") do (
for /f "delims=" %%B in (""!temp2!"") do (
endlocal
set "var1=%%~A"
set "var2=%%~B"
)
)
绝对查看jeb's answer以获得适用于所有情况下所有可能值的安全防弹技术。
2017-08-21 - 新功能RETURN.BAT
我使用DosTips用户jeb开发a batch utility called RETURN.BAT,可以用来退出脚本或调用例程,并在ENDLOCAL屏障中返回一个或多个变量。非常酷: - )
以下是代码的3.0版。我很可能不会将此代码保持最新。最好按照链接确保您获得最新版本,并查看一些示例用法。
<强> RETURN.BAT 强>
::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
答案 1 :(得分:13)
@ECHO OFF
SETLOCAL
REM Keep in mind that BAR in the next statement could be anything, including %1, etc.
SET FOO=BAR
ENDLOCAL && SET FOO=%FOO%
答案 2 :(得分:8)
dbenham的answer是“普通”字符串的一个很好的解决方案,但是如果在ENDLOCAL之后启用了延迟扩展,则它会以惊叹号!
失败(dbenham说这个太)。
但它总是会因为嵌入式换行等一些棘手的内容而失败 因为FOR / F会将内容分成多行 这将导致奇怪的行为,endlocal将执行多次(对于每个换行),因此代码不是防弹。
存在防弹解决方案,但它们有点乱:-)
宏版存在SO:Preserving exclamation ...,使用它很容易,但要阅读它是......
或者您可以使用代码块,您可以将其粘贴到您的功能中 Dbenham和我在线程Re: new functions: :chr, :asc, :asciiMap中发展了这项技术 还有对这种技术的解释
@echo off
setlocal EnableDelayedExpansion
cls
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
set LF=^
rem TWO Empty lines are neccessary
set "original=zero*? %%~A%%~B%%~C%%~L!LF!one&line!LF!two with exclam^! !LF!three with "quotes^&"&"!LF!four with ^^^^ ^| ^< ^> ( ) ^& ^^^! ^"!LF!xxxxxwith CR!CR!five !LF!six with ^"^"Q ^"^"L still six "
setlocal DisableDelayedExpansion
call :lfTest result original
setlocal EnableDelayedExpansion
echo The result with disabled delayed expansion is:
if !original! == !result! (echo OK) ELSE echo !result!
call :lfTest result original
echo The result with enabled delayed expansion is:
if !original! == !result! (echo OK) ELSE echo !result!
echo ------------------
echo !original!
goto :eof
::::::::::::::::::::
:lfTest
setlocal
set "NotDelayedFlag=!"
echo(
if defined NotDelayedFlag (echo lfTest was called with Delayed Expansion DISABLED) else echo lfTest was called with Delayed Expansion ENABLED
setlocal EnableDelayedExpansion
set "var=!%~2!"
rem echo the input is:
rem echo !var!
echo(
rem ** Prepare for return
set "var=!var:%%=%%~1!"
set "var=!var:"=%%~2!"
for %%a in ("!LF!") do set "var=!var:%%~a=%%~L!"
for %%a in ("!CR!") do set "var=!var:%%~a=%%~3!"
rem ** It is neccessary to use two IF's else the %var% expansion doesn't work as expected
if not defined NotDelayedFlag set "var=!var:^=^^^^!"
if not defined NotDelayedFlag set "var=%var:!=^^^!%" !
set "replace=%% """ !CR!!CR!"
for %%L in ("!LF!") do (
for /F "tokens=1,2,3" %%1 in ("!replace!") DO (
ENDLOCAL
ENDLOCAL
set "%~1=%var%" !
@echo off
goto :eof
)
)
exit /b
答案 3 :(得分:2)
我也想为此做出贡献并告诉你如何传递一组类似数组的变量:
@echo off
rem clean up array in current environment:
set "ARRAY[0]=" & set "ARRAY[1]=" & set "ARRAY[2]=" & set "ARRAY[3]="
rem begin environment localisation block here:
setlocal EnableExtensions
rem define an array:
set "ARRAY[0]=1" & set "ARRAY[1]=2" & set "ARRAY[2]=4" & set "ARRAY[3]=8"
rem `set ARRAY` returns all variables starting with `ARRAY`:
for /F "tokens=1,* delims==" %%V in ('set ARRAY') do (
if defined %%V (
rem end environment localisation block once only:
endlocal
)
rem re-assign the array, `for` variables transport it:
set "%%V=%%W"
)
rem this is just for prove:
for /L %%I in (0,1,3) do (
call echo %%ARRAY[%%I]%%
)
exit /B
代码有效,因为if defined
块中setlocal
查询了第一个数组元素,实际定义了endlocal
块,因此setlocal
只执行一次。对于所有连续循环迭代,if defined
块已经结束,因此ARRAY
计算为 FALSE 。
这取决于至少分配了一个数组元素,或者实际上,在setlocal
/ {{1}内分配了至少一个名称以endlocal
开头的变量阻止。如果其中不存在,则不会执行endlocal
。在setlocal
块之外,不必定义此类变量,否则if defined
会多次计算 TRUE ,因此,endlocal
会被多次执行
要克服这些限制,您可以使用类似标志的变量,根据:
ARR_FLAG
命令之前清除标记变量,例如setlocal
:set "ARR_FLAG="
; setlocal
/ endlocal
块内定义标志变量,即为其分配一个非空值(紧接在for /F
循环之前):{{1 }}; set "ARR_FLAG=###"
命令行更改为:if defined
; if defined ARR_FLAG (
选项字符串更改为for /F
; "delims="
循环中的set
命令行更改为:for /F
; 答案 4 :(得分:1)
如下所示(我还没有测试过):
@echo off
setlocal
set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts
pushd %scripts%
endlocal
call .\activate.bat
popd
由于以上不起作用(见Marcelo的评论),我可能会这样做:
set uniquePrefix_base=compute directory
set uniquePrefix_pkg=compute sub-directory
set uniquePrefix_scripts=%uniquePrefix_base%\%uniquePrefix_pkg%\Scripts
set uniquePrefix_base=
set uniquePrefix_pkg=
call %uniquePrefix_scripts%\activate.bat
set uniquePrefix_scripts=
其中uniquePrefix_被选为在您的环境中“几乎肯定”唯一。
您还可以在bat文件的条目上测试“uniquePrefix _...”环境变量在输入时未按预期定义 - 如果不是,则可以退出并显示错误。
我不喜欢将BAT复制到TEMP目录作为一般解决方案,因为(a)具有&gt; 1调用者的竞争条件的可能性,以及(b)在一般情况下BAT文件可能正在访问使用相对于其位置的路径的其他文件(例如%~dp0 .. \ somedir \ somefile.dat)。
以下丑陋的解决方案将解决(b):
setlocal
set scripts=...whatever...
echo %scripts%>"%TEMP%\%~n0.dat"
endlocal
for /f "tokens=*" %%i in ('type "%TEMP%\%~n0.dat"') do call %%i\activate.bat
del "%TEMP%\%~n0.dat"
答案 5 :(得分:1)
为了幸存多个变量:如果你选择使用“经典”
ENDLOCAL & SET VAR=%TEMPVAR%
有时会在其他回复中提及(并且对某些回复中显示的缺点得到解决或不是问题感到满意),请注意您可以执行多个变量,即ENDLOCAL & SET var1=%local1% & SET var2=%local2%
。< / p>
我分享这个,因为除了下面的链接网站,我只看到用单个变量说明的“技巧”,和我一样,有些人可能错误地认为它只对单个变量“起作用”。
答案 6 :(得分:0)
回答我自己的问题(如果没有其他答案曝光,并避免重复我已经知道的那个)......
在调用包含调用目标批处理文件的命令的endlocal
之前创建临时批处理文件,然后在endlocal
之后调用并删除它:
echo %scripts%\activate.bat > %TEMP%\activate.bat
endlocal
call %TEMP%\activate.bat
del %TEMP%\activate.bat
这太丑了,我想羞愧地低下头。我们非常欢迎更好的答案。
答案 7 :(得分:-1)
这个怎么样。
@echo off
setlocal
set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts
(
endlocal
"%scripts%\activate.bat"
)