Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make an environment variable survive ENDLOCAL

I have a batch file that computes a variable via a series of intermediate variables:

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

The script on the last line isn't called, because it comes after endlocal, which clobbers the scripts environment variable, but it has to come after endlocal because its purpose is to set a bunch of other environment variables for use by the user.

How do I call a script who's purpose is to set permanent environment variables, but who's location is determined by a temporary environment variable?

I know I can create a temporary batch file before endlocal and call it after endlocal, which I will do if nothing else comes to light, but I would like to know if there is a less cringe-worthy solution.

like image 530
Marcelo Cantos Avatar asked Jul 16 '10 05:07

Marcelo Cantos


People also ask

What is ENDLOCAL in batch file?

There is an implicit endlocal command at the end of a batch file. If command extensions are enabled (command extensions are enabled by default), the endlocal command restores the state of command extensions (that is, enabled or disabled) to what it was before the corresponding setlocal command was run.

What does SETLOCAL EnableDelayedExpansion mean?

ENABLEDELAYEDEXPANSION is a parameter passed to the SETLOCAL command (look at setlocal /? ) Its effect lives for the duration of the script, or an ENDLOCAL : When the end of a batch script is reached, an implied ENDLOCAL is executed for any outstanding SETLOCAL commands issued by that batch script.

What does SETLOCAL do in batch file?

Use setlocal to change environment variables when you run a batch file. Environment changes made after you run setlocal are local to the batch file. The Cmd.exe program restores previous settings when it encounters an endlocal command or reaches the end of the batch file.

What is ENDLOCAL?

The endlocal command is used to stop the localization of the environment changes enabled by the setlocal command.


1 Answers

The ENDLOCAL & SET VAR=%TEMPVAR% pattern is classic. But there are situations where it is not ideal.

If you do not know the contents of TEMPVAR, then you might run into problems if the value contains special characters like < > & or|. You can generally protect against that by using quotes like SET "VAR=%TEMPVAR%", but that can cause problems if there are special characters and the value is already quoted.

A FOR expression is an excellent choice to transport a value across the ENDLOCAL barrier if you are concerned about special characters. Delayed expansion should be enabled before the ENDLOCAL, and disabled after the ENDLOCAL.

setlocal enableDelayedExpansion set "TEMPVAR=This & "that ^& the other thing" for /f "delims=" %%A in (""!TEMPVAR!"") do endlocal & set "VAR=%%~A" 

Limitations:

  • If delayed expansion is enabled after the ENDLOCAL, then the final value will be corrupted if the TEMPVAR contained !.

  • values containing a lineFeed character cannot be transported

If you must return multiple values, and you know of a character that cannot appear in either value, then simply use the appropriate FOR /F options. For example, if I know that the values cannot contain |:

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

If you must return multiple values, and the character set is unrestricted, then use nested FOR /F loops:

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

Definitely check out jeb's answer for a safe, bullet proof technique that works for all possible values in all situations.

2017-08-21 - New function RETURN.BAT
I've worked with DosTips user jeb to develop a batch utility called RETURN.BAT that can be used to exit from a script or called routine and return one or more variables across the ENDLOCAL barrier. Very cool :-)

Below is version 3.0 of the code. I most likely will not keep this code up-to-date. Best to follow the link to make sure you get the latest version, and to see some example usage.

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 
like image 110
dbenham Avatar answered Sep 17 '22 18:09

dbenham