2010-07-16 71 views
21

我有通過一系列中間變量計算變量的批處理文件:做一個環境變量生存ENDLOCAL

@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之後調用它,但如果沒有其他任何東西出現,我會這樣做,但是我想知道是否有一個不值得的解決方案。

+1

我喜歡下面的所有答案,顯示如何處理各種邊界情況,不會與您的具體問題發揮作用。 (你的最終結果必須是一個合法的目錄,所以它不可能有'!'或';'或其他元字符)。它表明我們所有人都更關心教學,而不是僅僅通過一個答案快速地放風。而且,我的朋友們,是StackOverflow。 – 2015-07-21 20:42:36

+0

我通過在這裏使用reg.exe和volatile註冊表回答了這個問題:https://stackoverflow.com/questions/26246151/setlocal-enabledelayedexpansion-causes-cd-and-pushd-to-not-persist/45369743#45369743 – mosh 2017-07-28 13:32:17

+0

有趣的是,我懷疑被調用的腳本只需要引用它自己的路徑,即IE,「Activate.bat」需要知道它從哪裏被調用,並且將該變量指定爲「腳本」以用於其內部的未來調用。 – 2017-11-16 15:23:33

回答

20

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" 
) 

如果必須返回多個值,字符集是不受限制的,然後使用嵌套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年8月21日 - 新功能RETURN.BAT
我已經與DosTips用戶傑布合作開發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 
+2

Btw的答案。奇怪的語法是多餘的,它會因爲一個空的tempvar而失敗。更好的是'FOR/F'delims =「%% A in(」「!tempvar!」「)do endlocal&set」var = %%〜A「' – jeb 2013-10-31 11:54:25

+1

@jeb - 多餘?如果值以';'開始,那麼它將被跳過。附帶引號的好主意。另一個選擇是在發出SETLOCAL之前將該值初始化爲空,除非通過多個FOR/F循環返回多個值可能會失敗。 – dbenham 2013-10-31 12:07:31

+1

事實上,它不是多餘的,但與雙引號版本相比,因爲eol字符沒有問題,也沒有空值。 – jeb 2013-10-31 12:10:51

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 

由於上述不工作(見馬塞洛的評論),我可能會做如下:

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)具有> 1個調用者的競爭條件的可能性,並且(b)在一般情況下BAT文件可能正在訪問其他文件使用相對於其位置的路徑(例如%〜dp0 .. \ somedir \ somefile.dat)。

下醜陋的解決方案將解決(二):

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" 
+1

靈感來源!我喜歡它。不幸的是,它不起作用。我試了一下,發現endlocal在setlocal是local之前也回到了工作目錄。 – 2010-07-16 06:26:58

+0

請參閱[匿名'答案](http://stackoverflow.com/questions/3262287/make-an-environment-variable-survive-endlocal/3262891#3262891)一個很好的方式做到這一點。它變得亂七八糟的變量,但工作正常,無需創建任何文件。 – Joey 2010-07-16 08:24:18

0

要回答我的問題(如果沒有其他的答案水落石出,並避免了一個我已經知道的重複).. 。

創建調用endlocal包含命令來調用目標批處理文件之前臨時批處理文件,然後endlocal調用後,並刪除它:

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

endlocal 

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

這太難看了,我想把我的頭埋在恥辱之中。更好的答案是最受歡迎的。

12
@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

+1。就是那個。它的工作原理是因爲環境變量在解析行時被擴展,這意味着一旦這行被運行,首先執行'endlocal',之後'set'中的變量已經被擴展。 – Joey 2010-07-16 08:22:07

+1

但解決方案可能會失敗,它取決於%FOO%中的內容,請參閱dbenham – jeb 2011-11-24 10:42:19

8

dbenham的answer「正常」串一個很好的解決方案,但如果延遲擴展ENDLOCAL後啓用它失敗感嘆號!(dbenham說,這太)。

但是,由於FOR/F會將內容分割成多行,因此它總是會失敗並出現一些棘手的內容,例如嵌入式換行符,

這會導致奇怪的行爲,endlocal會執行多次(對於每一行換行),所以代碼不是防彈的。

存在着防彈解決方案,但他們有點凌亂:-)
一個宏版本存在SO:Preserving exclamation ...,使用很容易,但要讀它是...

或者你可以使用代碼塊,您可以將其粘貼到您的功能。
Dbenham,我在線程Re: new functions: :chr, :asc, :asciiMap開發了這個TECHNIC,
也有此TECHNIC

@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 
0

我想參與這也告訴你解釋如何可以越過類似陣列的集的變量:

@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,2 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的計算結果爲FALSE

這取決於如下事實:至少有一個數組元素被分配,或者實際上,在setlocal/endlocal塊內至少有一個定義的變量名稱以ARRAY開頭。如果其中不存在,則endlocal不會被執行。在setlocal塊之外,不需要定義這樣的變量,因爲否則,if defined將多次計算爲TRUE,因此endlocal會多次執行。

爲了克服這個限制,你可以使用一個標誌般的變量,根據這個:

  • 明確的標誌變量,說ARR_FLAGsetlocal命令之前:set "ARR_FLAG=";
  • setlocal/endlocal塊內定義了標誌變量,即爲其分配非空值(緊接在for /F循環之前):set "ARR_FLAG=###";
  • if defined命令行更改爲:if defined ARR_FLAG (;
  • ,那麼你也可以做可選:
    • 變化for /F選項字符串"delims=";
    • for /F循環中的set命令行更改爲:set "%%V";
-1

這個怎麼樣。

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

爲什麼在發佈之前沒有對它進行測試?它根本不起作用 – jeb 2016-10-01 14:06:32

0

倖存的多個變量:如果你選擇去與「經典」
ENDLOCAL & SET VAR =%的TempVar%
在這裏其他的反應有時會提到的(和感到滿意的是在一些所示的缺點的答覆解決或不是問題),請注意,你可以做多個變量,一拉
ENDLOCAL & SET VAR1 =%LOCAL1%& SET VAR2 =%local2%
我同意這一點,因爲不是鏈接網站等在下面,我只看到了一首演唱的「訣竅」變量,和我一樣,有些人可能錯誤地認爲它只對一個變量「起作用」。 https://ss64.com/nt/endlocal.html