Setting an environment variable with delayed expansion enabled - batch-file

I have the following script that should set an environment variable %NUMBER%:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET processed=0
IF "%~1"=="latest" (
CALL :LATEST_BUILD_NUMBER %~2
ECHO "->%build_number% RETURNS NO DATA"
ECHO "->!build_number! GIVES THE BUILD NUMBER BACK"
CALL :SET_ENVIRONMENT_VARIABLE !build_number!
SET processed=1
)
:END
ENDLOCAL
ECHO ON
#EXIT /B 0
:SET_ENVIRONMENT_VARIABLE
ECHO SET "NUMBER=%~1"
SET "NUMBER=%~1"
#EXIT /B 0
:LATEST_BUILD_NUMBER
REM CALCULATE THE BUILDNUMBER, NOW JUST SET IT
set build_number=589
EXIT /B 0
When I do run this code in a command prompt window, and do an echo of the NUMBER variable, it is not set in console window.
d:\> ECHO %NUMBER%
%NUMBER%
How should I do this correctly?

Each setlocal creates a new variable scope.
This scope will be destroyed with endlocal or implicit by exiting the batch file.
You have to safe your variables over the scope lifetime (often called endlocal barrier).
In a called function it's not possible to ENDLOCAL an outer SETLOCAL (not impossible, but only with advanced technics).
So you have to modify your code.
#ECHO OFF
SETLOCAL EnableExtensions EnableDelayedExpansion
SET processed=0
SET build_number=0
IF "%~1"=="latest" (
CALL :LATEST_BUILD_NUMBER %~2
ECHO -^>%build_number% RETURNS NO DATA
ECHO -^>!build_number! GIVES THE BUILD NUMBER BACK
REM *** CALL :SET_ENVIRONMENT_VARIABLE !build_number!
SET processed=1
)
:END
(
ENDLOCAL
set "number=%build_number%"
EXIT /B 0
)
...
The trick is to build an ENDLOCAL-Block with parenthesis, as the percent expansion is evaluated when the block is parsed, so the value will be available after the ENDLOCAL is executed.

Related

Using local variables for a function which "returns" a result

I'm trying to use local variables in a function but it doesn't work as expected.
#echo off
setlocal
call :Sum %1, %2, sum
echo %sum%
exit /b 0
:Sum
setlocal
set "a=%~1"
set "b=%~2"
set /a "%~3=%a%+%b%"
endlocal
exit /b 0
Here is the invocation of the script:
>test.bat 1 2
ECHO is off.
Any clues?
If the expected result is to put sum of arguments into a variable then you'll need to remove setlocal/endlocal inside of :Sum.
:Sum
set "a=%~1"
set "b=%~2"
set /a "%~3=%a%+%b%"
exit /b 0
Or you could try a trick from this answer Batch script make setlocal variable accessed by other batch files
Also using of commas as argument delimiters for call isn't encouraged.
You get ECHO is off. because %sum% is not set. Here's an working example:
#echo off
SETLOCAL
CALL :Sum %1, %2, sum
ECHO %sum%
EXIT /B %ERRORLEVEL%
:Sum
SET /A "%~3 = %~1 + %~2"
EXIT /B 0
As mentioned by #montonero, the following hack* solves the problem:
#echo off
setlocal
call :Sum %1 %2 sum
echo %sum%
exit /b 0
:Sum
setlocal
set "a=%~1"
set "b=%~2"
endlocal & (
set /a "%~3=%a%+%b%"
)
exit /b 0
The command line endlocal & ... works due to the fact that variables are expanded before the command line is executed, thus creating a bridge between the local and the parent scope.
*https://stackoverflow.com/a/18291599/337149

Passing exclamation marks as parameters in batch subroutine call

Thanks to this community I have finally learned how to escape exlamation marks for immediate use in a batch delayedExpansion block.
(use two escape carets not just one, awesome)
But I can't seem to find or figure out how to pass the contents of a variable containing an exclamation mark as parameter to a batch subroutine.
example:
#echo off
setLocal EnableDelayedExpansion
set variable=Hello^^!
echo "!variable!"
call :subroutine "!variable:^^!=^^!!"
pause
exit
:subroutine
echo "%~1"
exit/b
Output:
"Hello!"
"Hello"
Press any key to continue . . .
I want the second "Hello" to include an exclamation mark.
I have tried various permutations of substring replacement on line 5 to no avail.
help
You need a different way for the variable replacing, and much more carets.
#echo off
setLocal EnableDelayedExpansion
set variable=Hello^^!
echo "!variable!"
call :subroutine %variable:!=^^^^^^^^^^!%
exit /b
:subroutine
echo %~1
exit /b
Or with quotes:
call :subroutine "%variable:!=^^^!%"
In your function you need to expand %1 without any quotes, as the number of carets are always odd in a CALL parameter.
But at all it's a bad idea to try such things.
I agree with Aacini, that you should use pass by reference instead.
This is the only way to handle any possible content.
#echo off
setLocal EnableDelayedExpansion
set variable=Hello^^!
echo "!variable!"
call :subroutine variable
exit /b
:subroutine
echo !%1!
exit /b
Maybe the problem is not how to pass the data to the subroutine, but how to get the data inside it
#echo off
setlocal enabledelayedexpansion
set "var=Hello^!"
setlocal disabledelayedexpansion
echo %var%
call :echo1 %var%
call :echo2 var
endlocal
setlocal enabledelayedexpansion
echo !var!
call :echo1 !var!
call :echo2 var
endlocal
endlocal
exit /b
:echo1
setlocal disabledelayedexpansion
echo %~1
endlocal
goto :eof
:echo2
setlocal enabledelayedexpansion
echo !%~1!
endlocal
goto :eof

Batch file: Variable within a variable

I have a complex string I'm trying to parse. I have a short subroutine to fine the index of two specific characters, and I need the string in between. So the question is:
How do I use variables inside string manipulation of another variable?
Here's some sample code:
#ECHO off
set start=2
set end=5
set str=Hello World
echo %str:~2,5%
echo %str:~%start%,%end%%
How do I get the second echo to display what the first echo displays? (right now second echo is just showing what I want, it'll crash as is)
#ECHO off
set start=2
set end=5
set str=Hello World
setlocal enableDelayedExpansion
echo %str:~2,5%
echo !str:~%start%,%end%!
endlocal
or (the worst way)
#ECHO off
set start=2
set end=5
set str=Hello World
echo %str:~2,5%
call echo %%str:~%start%,%end%%%
or (can be used if start and end definition and the substringing are all in brackets context)
#ECHO off
set start=2
set end=5
set str=Hello World
echo %str:~2,5%
setlocal enableDelayedExpansion
for /f "tokens=1,2" %%a in ("%start% %end%") do echo !str:~%%a,%%b!
endlocal
or (also a bad way)
#ECHO off
set start=2
set end=5
set str=Hello World
call :substr "%str%" %start% %end%
goto :eof
:substr
setlocal enableDelayedExpansion
set "_str=%~1"
echo !_str:~%2,%3!
rem without delayedExpansion
rem call echo %%_str:~%2,%3%%
endlocal
goto :eof
CALL echo %%str:~%start%,%end%%%
in place of your version. No need for enabledelayedexpansion

Delayed expansion not working for a for loop

I have this batch script and I am calling another batch script "Strip_Batch.bat" and passing %Stripped_Name% variable as parameter. But this parameter doesnt work. Any idea how I can pass this variable?
#echo off
setlocal enableextensions enableDelayedExpansion
set CONFIGURATIONS=HAVING_FUN_WITH_COLLEGUES
for %%i in (%CONFIGURATIONS%) do (
set Original_Name=%%i
echo !Original_Name!
set Stripped_Name=!Original_Name:~0,-14!
echo !Stripped_Name!
call Strip_Batch.bat %%i %Stripped_Name%
if errorlevel 1 goto error_exit
)
:the_end
endlocal
exit /b 0
:error_exit
endlocal
exit /b 1
according to my comment I'd suggest first:
call Strip_Batch.bat %%i !Stripped_Name!
and second (better, works also for parameter with spaces):
call Strip_Batch.bat "%%i" "!Stripped_Name!"

Batch (.bat): get the name of the first script, not the current one

I have first.bat and second.bat.
first.bat is: call second.bat
Second is: echo %~n0 (displays filename of the executing batch)
The output is Second.bat, but I want it to display the caller filename, not it's own.
Is this possible?
This batch detects the name of the caller script or even if it's called directly from the command line
#echo off
setlocal DisableDelayedExpansion
set "func=%~0"
for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
if ":" == "%func:~0,1%" (
goto %func%
)
REM *** Get the name of the caller
(
(goto) 2>nul
setlocal DisableDelayedExpansion
call set "caller=%%~f0"
call set _caller=%%caller:*%%~f0=%%
if defined _caller (
set "callType=batch"
call "%~d0\:mainFunc\..%~pnx0" %*
) ELSE (
set "callType=cmd-line"
cmd /c "call "%~d0\:mainFunc\..%~pnx0" %*"
)
endlocal
)
echo NEVER REACHED
exit /b
:mainFunc
echo :mainFunc of %~nx0 arg1=%1 is called from '%caller%'/%callType%
exit /b
It uses the fact, that a (goto) statement will remove one level from the stack.
This results into leaving the current batch file and %~f0 will be the name of the caller script (and %~0 the current function of that batch) or the text %~f0 in the case of called from the command line.
Then the own script is called again with "%~d0\:mainFunc\..%~pnx0"
External Script
For easy use you could add a helper batch file.
Use it in your own scripts with this line
#echo off
<:GetCaller <nul call GetCaller.bat myCallerVar
echo This batch was called from "%myCallerVar%"
Name the helper batch file GetCaller.bat
#echo off
setlocal DisableDelayedExpansion
set "func=%~0"
for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
if ":" == "%func:~0,1%" (
goto %func%
)
REM *** STEP1
REM *** Get the filename of the caller of this script, needed for later restart that
(
(goto) 2>nul
setlocal DisableDelayedExpansion %= it could be reenabled by the GOTO =%
set "_returnVar=%~1"
call set "_lastCaller=%%~f0"
call set "_argToLastCaller=%%*"
call "%~d0\:Step2\..%~pnx0" %*
)
exit /b %= This is never reached =%
:Step2
REM *** STEP2
REM *** Get the filename/cmd-line of the caller of the script
(
(goto) 2>nul
(goto) 2>nul
setlocal DisableDelayedExpansion %= it could be reenabled by the GOTO =%
set "_returnVar=%_returnVar%"
set "_lastCaller=%_lastCaller%"
set "_argToLastCaller=%_argToLastCaller%"
call set "caller=%%~f0"
call set _caller=%%caller:*%%~f0=%%
if defined _caller (
set "callType=batch"
call "%~d0\:Step3batch\..%~pnx0"
) ELSE (
set "callType=cmd-line"
cmd /c "call "%~d0\:Step3batch\..%~pnx0" "
)
endlocal
)
exit /b %= This is never reached =%
:Step3batch
REM *** STEP3 Restart the requester batch, but jump to the label :GetCaller
call :GetCaller
exit /b %= This is never reached =%
:GetCaller
REM *** This uses the trick, that starting a batch without CALL will jump to the last used label
if "%_returnVar%" NEQ "" set "%_returnVar%=%_caller%"
%_lastCaller% %_argToLastCaller%
echo #6 never reached
I think the easiest way of doing this would be to pass the filename of the first batch as a parameter to the second, like this.
REM First.bat
call Second.bat %~n0
REM Second.bat
echo %1
Hope this helps!
Store the value of %~n0 in a environment variable before calling second.bat

Resources