windows batch parentheses scope - windows

How can I set result variable in a scope surrounded with parantheses ('if' or 'for'-loop). The result is correct (>> RESULT: aaa = bbb), when procedure is called directly, and fails when used in for-loop or if-statement (>> RESULT: ccc = ).
:: =====================================
#setlocal
#echo off
#rem (1)
call :testReturn aaa
echo RESULT: aaa = %aaa%
#rem (2)
if "1" == "1" (
call :testReturn ccc
echo RESULT: ccc = %ccc%
)
goto :eof
:testReturn
set %~1=bbb
exit /b
endlocal
Thanks!!

When a compound statement enclosed in parentheses is to be executed,
the statement is first parsed from the open parenthesis all of the
way to the matching close-parenthesis.
At this time, any %var% is replaced by that var's value from the
environment AT THE TIME IT IS PARSED (ie its PARSE-TIME value.)
THEN if the statement seems valid, it is executed.
There are three common ways of accessing the RUN-TIME value of the
variable (as a FOR loop executes, for instance.)
1/ SETLOCAL ENABLEDELAYEDEXPANSION which switches to a mode where
!var! may be used to access the runtime value of var
2/ CALL set var2=%%var%% to set the value of var2 from the
runtime value of var
3/ Executing a subroutine, internal or external within which %var%
will be the runtime value.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%i IN (1 2 3) DO (
ECHO START of run %%i
ECHO using ^!time^! : !time! - PARSE TIME was %time%
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
timeout /t 5
ECHO using ^!time^! : !time!
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
ECHO END of run %%i
ECHO.
)
GOTO :eof
:report
ECHO :report says TIME is %TIME%
GOTO :eof
A few items to note:
The instruction
IF ERRORLEVEL n echo errorlevel is n OR GREATER
ALWAYS interprets the RUN-TIME value of ERRORLEVEL
IF SET VAR ALWAYS interprets the RUN-TIME value of VAR
The magic variables like ERRORLEVEL and TIME should never
be SET. If you execute
SET ERRORLEVEL=dumb
then ERRORLEVEL will adopt the value dumb because the current
value in the environment takes priority over the system-assigned value.

inside a code block (=surrounded with parantheses) you need delayed expansion and !variables!, not %variables%:
:: =====================================
#setlocal
#echo off
#rem (1)
call :testReturn aaa
echo RESULT: aaa = %aaa%
#rem (2)
if "1" == "1" (
call :testReturn ccc
setlocal enabledelayedexpansion
echo RESULT: ccc = !ccc!
endlocal
)
goto :eof
:testReturn
set %~1=bbb
exit /b
endlocal

Related

Print variable in batch file

I am trying to print a variable in parenthesised code which is assigned a value using other variable in batch file.
Here is my code
#echo off
SETLOCAL enableDelayedExpansion
CALL initialize
CALL fun
:fun (
#echo off
Setlocal EnableDelayedExpansion
Set "SOMEVAR=!OTHERVAR!"
ECHO ..%SOMEVAR%
EXIT /B 0
)
:initialize (
set SOMEVAR=somevalue
EXIT /B 0
)
The output is just
..
How do i fix it so that i can assign value to somevar?
Edit1: If i now try to print in following way it does its job
ECHO ..!SOMEVAR!
But my script uses lot of %SOMEVAR%. Does that mean i need to change them all?
Note: Othervar is initialzed in other function and it does show proper value if it is echoed.
Since the code portion containing echo %SOMEVAR% is in between parenthesis, the variable is expanded before being set (consult this post for a good explanation).
There are the following options to avoid that:
to expand it like !SOMEVAR! (delayed expansion), or
to avoid the parenthesis:
#echo off
SETLOCAL EnableDelayedExpansion
CALL initialize
CALL fun
exit /B
:fun
Setlocal EnableDelayedExpansion
Set "SOMEVAR=!OTHERVAR!"
ECHO ..%SOMEVAR%
EXIT /B 0
:initialize
set SOMEVAR=somevalue
EXIT /B 0
Note the additional exit /B in the above code snippet after the call statements, which prevents from falling into the code below unintentionally.
Does this work any closer to your expectations? Note that SOMEVAR will not be returned to your shell environment unless an ENDLOCAL block is used.
C:>set OTHERVAR=0123456789
C:>type g2.bat
#echo off
SETLOCAL enableDelayedExpansion
CALL:initialize
CALL:fun
EXIT /B 0
:fun (
rem #echo off
Setlocal EnableDelayedExpansion
Set "SOMEVAR=!OTHERVAR!"
ECHO ..%SOMEVAR%
GOTO :EOF
)
:initialize (
set SOMEVAR=somevalue
GOTO :EOF
)
C:>g2.bat
..0123456789

IF, CALL, EXIT and %ERRORLEVEL% in a .bat

Can anyone please help me understand the behaviour of %ERRORLEVEL% variable and why it's not being set after a CALL while being inside an IF, i.e. the ECHO %ERRORLEVEL%.2 line?
#ECHO OFF
SET ERRORLEVEL
VERIFY > NUL
ECHO %ERRORLEVEL%.0
IF ERRORLEVEL 1 ECHO SNAFU
IF %ERRORLEVEL% == 0 (
ECHO %ERRORLEVEL%.1
CALL :FOO
ECHO %ERRORLEVEL%.2
IF ERRORLEVEL 42 ECHO 42.3
)
GOTO :EOF
:FOO
EXIT /B 42
GOTO :EOF
STDOUT
C:\Users\Ilya.Kozhevnikov\Dropbox>foo.bat
Environment variable ERRORLEVEL not defined
0.0
0.1
0.2
42.3
However, without IF the %ERRORLEVEL% variable is set as expected.
#ECHO OFF
SET ERRORLEVEL
VERIFY > NUL
ECHO %ERRORLEVEL%.0
IF ERRORLEVEL 1 ECHO SNAFU
REM IF %ERRORLEVEL% == 0 (
ECHO %ERRORLEVEL%.1
CALL :FOO
ECHO %ERRORLEVEL%.2
IF ERRORLEVEL 42 ECHO 42.3
REM )
GOTO :EOF
:FOO
EXIT /B 42
GOTO :EOF
STDOUT
C:\Users\Ilya.Kozhevnikov\Dropbox>foo.bat
Environment variable ERRORLEVEL not defined
0.0
0.1
42.2
42.3
When the cmd parser reads a line or a block of lines (the code inside the parenthesis), all variable reads are replaced with the value inside the variable before starting to execute the code. If the execution of the code in the block changes the value of the variable, this value can not be seen from inside the same block, as the read operation on the variable does not exist, it was replaced with the value in the variable
To solve it, you need to enable delayed expansion, and, where needed, change the syntax from %var% to !var!, indicating to the parser that the read operation needs to be delayed until the execution of the command.
#ECHO OFF
setlocal enabledelayedexpansion
SET ERRORLEVEL
VERIFY > NUL
ECHO %ERRORLEVEL%.0
IF ERRORLEVEL 1 ECHO SNAFU
IF %ERRORLEVEL% == 0 (
ECHO !ERRORLEVEL!.1
CALL :FOO
ECHO !ERRORLEVEL!.2
IF ERRORLEVEL 42 ECHO 42.3
)
GOTO :EOF
:FOO
EXIT /B 42
GOTO :EOF
MC ND answered the question already well.
Here is an alternative code showing both: expanded and delayed expansion of ERRORLEVEL.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
VERIFY > NUL
ECHO !ERRORLEVEL!.0 delayed
ECHO %ERRORLEVEL%.0 expanded
IF ERRORLEVEL 1 ECHO SNAFU
IF !ERRORLEVEL! == 0 (
ECHO !ERRORLEVEL!.1 delayed
ECHO %ERRORLEVEL%.1 expanded
CALL :FOO
ECHO !ERRORLEVEL!.2 delayed
ECHO %ERRORLEVEL%.2 expanded
)
ENDLOCAL
GOTO :EOF
:FOO
EXIT /B 42
GOTO :EOF
Microsoft describes the behavior of delayed expansion in help of command set which can be read in a command prompt window after entering set /? or help set

goto was not expected at this time batch

#echo off
:start
set string=
set lo=1
set a=0
set b=0
set cl=1
set cloop=
set google=0
set k=0
set r=0
set id=
set t=0
set f=0
set /p string=?
if defined string (
echo %string%
goto loop
) else (
echo please enter a string
goto start
)
:loop
set a=
for /f "tokens=%lo%" %%G IN ("%string%") DO echo %%G
if defined a (
echo %a%
set google=0
set /p cloop=<greetings.txt
pause
:cloop
set b=
for /f "tokens=%cl%" %%g IN ("%cloop%") DO set b=%%g
if defined string (
if %a%==%b% goto greetings
set /a cl=%cl%+1
goto cloop
) else (
set cl=0
set /a lo=%lo%+1
goto loop
)
) else (
goto google
)
:greetings
set f=0
set k=0
set r=0
set /p id=<greetingtone.dat
for /f "tokens=%cl%" %%g IN ("%id%") DO set t=%%g
start greeting.bat
call greeting.bat
goto talk
:google
echo not done yet
pause
goto start
i have narrowed it down to this line
if %a%==%b% goto greetings
when i remove it it runs
i have looked but i have no idea why it does not work
please help the greetings.txt has "hi hello grunt"
i think it might be the variables
If %a% or %b% are empty values, it is likely the compare is incomplete, and it is saying that the goto is not expected yet. For instance, if you type the following at a C:\ prompt:
c:\>if a== echo ok
c:\>if ==a echo ok
echo was unexpected at this time.
c:\>if == echo ok
ok was unexpected at this time.
c:\>
If you enclose each value in quotes, then the comparison will still work even if one or both of the values are empty. For instance:
if "%a%"=="%b%" goto greetings
The normal reason for that an unexpected word in an IF statement is that IF has a very specific syntax, IF item1 operator item2 actionstatement(s).
What is likely to be happening is that item1 AND item2 appear to be missing, so IF resolves that as IF == goto greetings. Since goto is not one of its known operators (==, equ, neq, leq, lss, geq, gtr`) then it complains.
The question from here is - why do %a% and %b% appear to be empty?
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed.
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered. In your case, that means the outermost IF - in if defined string.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
Next problem is using a label within a block. Not a good idea. On some versions, a label will terminate the block. Call a subroutine instead.
call :cloop
...
goto start
:cloop
(whatever needs to be done)
goto :eof
(note that :cloop and :EOF have a required colon. on cloop it means "this is an internal subroutine - it's in the cuurrent batchfile." :EOF is a predefined label understood by CMD to mean end of file.)

Batch script (Windows) string substitution with a twist

I know how to do a literal string substitution in a batch script. However, I have a specific situation where I need to substitute the value of a numeric variable
This is the script:
setlocal enableextensions enabledelayedexpansion
set /A L=2
:L1
if %L% EQU 0 goto :EOF
set STRING="THIS IS # TEST"
SET NEW=%STRING:#=%%L%
echo %NEW%
set /A L=%L% - 1
goto L1
I want it to display this:
THIS IS 2 TEST
THIS IS 1 TEST
But it ends up diplaying this instead:
THIS IS TEST2
THIS IS TEST1
Any tips on how to get it to do what I need?
Thanks.
Even the solutions of aphoria and Bali C will work, it's better to use
set "NEW=!STRING:#=%L%!"
As then the replacement will be done in the delayed expansion phase and not in the percent expansion phase.
This will also work with exclamation marks and carets in STRING
#echo off
set L=2
set "String=This is # test!"
setlocal EnableDelayedExpansion
set "NEW=!STRING:#=%L%!"
echo !NEW!
Your almost there, just change
SET NEW=%STRING:#=%%L%
to
SET NEW=%STRING:#=!L!%
You need to use !L! to use delayed expansion.
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET /A L=2
:L1
IF !L! EQU 0 goto :EOF
SET STRING=THIS IS # TEST
SET NEW=%STRING:#=!L!%
ECHO %NEW%
SET /A L=!L! - 1
GOTO L1
Actually, you don't have to use !L! everywhere, just in the SET NEW=%STRING:#!L!% line. I used it everywhere for visual consistency.
Just for fun. Here is how to do it without delayed expansion. :)
Using the call command to double expand the variables. Use double percents %% around the variable to evaluate second. Single percents % around the variable to evaluate first.
setlocal EnableExtensions
set /A L=2
:L1
if %L% EQU 0 goto :EOF
set "STRING=THIS IS # TEST"
call set "NEW=%%STRING:#=%L%%%"
echo %NEW%
set /A L=%L% - 1
goto L1
You may also use the STRING as a "format string" placing the desired variables in the right places enclosed in exclamation marks. This way, no further replacement of the values is needed, just display the format string in the usual way:
rem Define the "format string" with Delayed Expansion disabled:
set STRING=THIS IS !L! TEST
setlocal enableextensions enabledelayedexpansion
set /A L=2
:L1
if %L% EQU 0 goto :EOF
echo %STRING%
set /A L=L - 1
goto L1
Or, with no Delayed\Expansion:
set STRING=THIS IS %%L%% TEST
set /A L=2
:L1
if %L% EQU 0 goto :EOF
call echo %STRING%
set /A L=L - 1
goto L1
Antonio

How do I return a value from a function in a batch file?

I have the following batch file
#echo off
setlocal EnableDelayedExpansion
for /f "delims==" %%J in (File_List.txt) do (
call :setDate %%J MYD
echo/Date is: %MYD%
)
endlocal &goto :eof
:setDate
SETLOCAL ENABLEEXTENSIONS
echo %1
echo %~2
set NAME=%1
set NAME=%NAME:~-11%
echo %NAME%
echo %~2
endlocal&set %2=%NAME%&goto :eof
but with File_List.txt containing
file2012-05.csv
I get
file2012-05.csv
MYD
2012-05.csv
MYD
Date is:
How do I actually get the function setDate to return the value I want?
As I don't understand from your script what you want to achieve, I reply (for completeness) to the original subject: returning a value from a function.
Here is how I do it:
#echo off
set myvar=
echo %myvar%
call :myfunction myvar
echo %myvar%
goto :eof
:myfunction
set %1=filled
goto :eof
Result is:
empty
filled
The batch interpreter evaluates %MYD% at parse time, and at that time it's empty. That's why you have Delayed Expansion. Change this line:
echo/Date is: %MYD%
to this:
echo/Date is: !MYD!
and it will work like you want, because then it tells the interpreter to evaluate MYD at run-time.

Resources