代码之家  ›  专栏  ›  技术社区  ›  Marcelo Cantos

使环境变量成为局部变量

  •  23
  • Marcelo Cantos  · 技术社区  · 14 年前

    @echo off
    setlocal
    
    set base=compute directory
    set pkg=compute sub-directory
    set scripts=%base%\%pkg%\Scripts
    
    endlocal
    
    %scripts%\activate.bat
    

    scripts 环境变量,但它必须位于endlocal之后,因为它的目的是设置一组其他环境变量供用户使用。

    如果脚本的目的是设置永久的环境变量,但其位置是由临时环境变量决定的,那么如何调用该脚本呢?

    8 回复  |  直到 14 年前
        1
  •  25
  •   dbenham    7 年前

    这个 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,则最终值将被损坏 ! .

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

    如果必须返回多个值,并且知道某个字符不能出现在任何一个值中,则只需使用适当的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"
    )
    

    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


    a batch utility called RETURN.BAT 可用于退出脚本或调用的例程,并通过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
    
        2
  •  13
  •   user7116    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%
    
        3
  •  8
  •   Community CDub    7 年前

    answer dbenham是一个很好的解决方案 “正常” 字符串,但以感叹号失败 ! 如果在ENDLOCAL之后启用了延迟扩展(dbenham也这么说)。

    但它总是会失败一些棘手的内容,如嵌入式换行符,

    有防弹解决方案,但有点混乱:-)
    A 宏版本 存在 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
    
        4
  •  3
  •   aschipfl    5 年前

    我也想对此有所贡献,并告诉您如何传递一组类似数组的变量:

    @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 只执行一次。对于所有连续的循环迭代 块已结束,因此 计算结果为 .

    这取决于至少分配了一个数组元素,或者实际上至少定义了一个名称以开头的变量 ARRAY ,在 设置本地 / 结束本地 阻止。如果里面没有, 不会被处决。外面的 块,则不必定义此类变量,否则, 如果定义 计算结果为 真的 不止一次因此, 结束本地

    要克服此限制,可以使用类似于标志的变量,如下所示:

    • 清除flag变量,比如 ARR_FLAG 设置本地 set "ARR_FLAG=" ;
    • / 结束本地 for /F 循环优先): set "ARR_FLAG=###" ;
    • 更改 如果定义 命令行到: if defined ARR_FLAG ( ;
    • 然后您还可以选择执行以下操作:
      • 更改 用于/F 选项字符串到 "delims=" ;
      • set 中的命令行 用于/F 循环到: set "%%V" ;
        5
  •  1
  •   to StackOverflow    14 年前

    如下所示(我还没有测试过):

    @echo off 
    setlocal 
    
    set base=compute directory 
    set pkg=compute sub-directory 
    set scripts=%base%\%pkg%\Scripts 
    
    pushd %scripts%
    
    endlocal 
    
    call .\activate.bat 
    popd
    

    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_u216;…”环境变量是否按预期在条目上未定义-如果未定义,您可以退出并返回错误。

    以下难看的解决方案将解决(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"
    
        6
  •  1
  •   halfer    5 年前


    ENDLOCAL & SET VAR=%TEMPVAR% 有时在这里的其他回复中提到(并且对某些回复中显示的缺点得到解决或不是问题感到满意),请注意,您可以使用多个变量,即la ENDLOCAL & SET var1=%local1% & SET var2=%local2%

    我分享这一点是因为除了下面的链接站点之外,我只看到了用单个变量说明的“技巧”,而且像我自己一样,一些人可能错误地认为它只对单个变量“有效”。

    文件: https://ss64.com/nt/endlocal.html

        7
  •  0
  •   Marcelo Cantos    14 年前

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

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

    echo %scripts%\activate.bat > %TEMP%\activate.bat
    
    endlocal
    
    call %TEMP%\activate.bat
    del %TEMP%\activate.bat
    

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

        8
  •  -2
  •   Tony Stachnicki    8 年前

    这个怎么样。

    @echo off
    setlocal
    set base=compute directory
    set pkg=compute sub-directory
    set scripts=%base%\%pkg%\Scripts
    (
      endlocal
      "%scripts%\activate.bat"
    )