Batch file 使环境变量成为局部变量

Batch file 使环境变量成为局部变量,batch-file,environment-variables,Batch File,Environment Variables,我有一个通过一系列中间变量计算变量的批处理文件: @echo off setlocal set base=compute directory set pkg=compute sub-directory set scripts=%base%\%pkg%\Scripts endlocal %scripts%\activate.bat 最后一行上的脚本没有被调用,因为它位于endlocal之后,这会关闭脚本环境变量,但它必须位于endlocal之后,因为它的目的是设置一组供用户使用的其他环境变

我有一个通过一系列中间变量计算变量的批处理文件:

@echo off
setlocal

set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts

endlocal

%scripts%\activate.bat
最后一行上的脚本没有被调用,因为它位于endlocal之后,这会关闭
脚本
环境变量,但它必须位于endlocal之后,因为它的目的是设置一组供用户使用的其他环境变量

如何调用一个脚本,该脚本的目的是设置永久环境变量,但其位置由临时环境变量确定?


我知道我可以在endlocal之前创建一个临时批处理文件,并在endlocal之后调用它,如果没有其他发现,我会这样做,但我想知道是否有一个不那么棘手的解决方案。

类似于以下内容(我没有测试过):

由于上述方法不起作用(见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_uu在您的环境中“几乎肯定”是唯一的

您还可以在bat文件的条目上测试“uniquePrefix_389;…”环境变量在条目上未按预期定义-如果未定义,您可以退出并出错

我不喜欢将BAT复制到TEMP目录作为一般解决方案,因为(a)可能存在大于1个调用方的竞争条件,以及(b)在一般情况下,BAT文件可能使用相对于其位置的路径(例如%~dp0..\somedir\somefile.dat)访问其他文件

以下丑陋的解决方案将解决(b):

回答我自己的问题(以防没有其他答案出现,并避免重复我已经知道的答案)

在调用包含调用目标批处理文件的命令的
endlocal
之前创建一个临时批处理文件,然后在
endlocal
之后调用并删除它:

echo %scripts%\activate.bat > %TEMP%\activate.bat

endlocal

call %TEMP%\activate.bat
del %TEMP%\activate.bat

这太难看了,我真想羞愧地低下头。更好的答案是最受欢迎的。

ENDLOCAL&SET VAR=%TEMPVAR%模式非常经典。但也有一些情况并不理想

@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%
如果您不知道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包含
    ,则最终值将损坏

  • 无法传输包含换行符的值

如果必须返回多个值,并且知道某个字符不能出现在任何一个值中,则只需使用适当的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"
  )
)
明确检查安全,防弹技术,在所有情况下适用于所有可能的值

2017-08-21-新函数RETURN.BAT
我与DosTips用户jeb合作开发了一个可用于退出脚本或调用例程并跨ENDLOCAL屏障返回一个或多个变量的函数。很酷:-)

下面是代码的3.0版本。我很可能不会保持此代码的最新状态。最好按照链接,以确保获得最新版本,并查看一些示例用法

返回.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
dbenham函数对于“普通”字符串是一个很好的解决方案,但是它失败了,带有感叹号
如果在ENDLOCAL之后启用了延迟扩展(dbenham也这么说)

但对于一些棘手的内容,如嵌入式换行符,它总是会失败,
因为FOR/F将把内容分成多行。
这将导致奇怪的行为,endlocal将执行多次(对于每个换行),因此代码不是防弹的

有防弹的解决方案,但有点混乱:-)
存在一个宏版本,使用起来很容易,但阅读起来却很难

或者您可以使用代码块,您可以将其粘贴到函数中。
Dbenham和我在线程中开发了这项技术,
对于这种技术也有一些解释

@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
@echo关闭
setlocal EnableDelayedExpansion
cls
对于('copy/Z'%~dpf0“nul')中的/f%%a,请设置“CR=%%a”
设置LF=^
rem需要两条空行
设置“original=zero*?%%~A%%~B%%~C%%~L!LF!一行和一行!LF!二行和感叹词^!!LF!三行和引号^&”!如果!四个带^^^^^^^ ^ ^<^>()^&^^ ^ ^^“!LF!xxxxx加CR!CR!五!LF!六加^”Q ^“我还是六”
setlocal DisableDelayedExpansion
调用:lfTest结果原件
setlocal EnableDelayedExpansion
回显禁用延迟扩展的结果为:
如果!原创!结果!(回声OK)否则回声!结果!
调用:lfTest结果原件
回显启用延迟扩展的结果为:
如果!原创!结果!(回声OK)否则回声!结果!
回音------------------
回声!起初的
后藤:eof
::::::::::::::::::::
:lfTest
setlocal
设置“NotDelayedFlag=!”
回音(
如果定义了NotDelayedFlag(在禁用延迟扩展的情况下调用echo lfTest),则在启用延迟扩展的情况下调用echo lfTest
setlocal EnableDelayedExpansion
设置“var=!%~2!”
rem回波输入为:
雷姆回声!瓦尔!
回音(
rem**准备返回
设置“var=!var:%%=%%~1!”
设置“var=!var:=%%~2!”
对于(“!LF!”)中的%%a,请设置“var=!var:%%~a=%%~L!”
对于(“!CR!”)中的%%a,请设置“var=!var:%%~a=%%~3!”
rem**必须使用两个IF,否则%var%扩展无法正常工作
::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
@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
@echo off
setlocal
set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts
(
  endlocal
  "%scripts%\activate.bat"
)