Batch file if statement weirdness with variable assignment and expansion - batch-file

I have a batch file and I'm trying to work with some variables in an if statement. For example I set a variable either from command line parameter 1, or if there is no parameter 1 then I have a default value. Then, depending on whether the value is the default or an argument from the command line I perform some other function.
What I notice is that the variable I've assigned in the if statement isn't available after the if statement is over. Can someone explain why that is?
For example this will fail to echo what's in TEST1:
#echo off
setlocal ENABLEEXTENSIONS
if .%1==. (
set TEST1=NOTFOUND
echo %TEST1%
) else (
set TEST1=%1
echo %TEST1%
)
If I run that batch file with or without an argument it returns ECHO is off.. It thinks the variable is still empty.
If I attempt to get the variable after the if statement, it works:
#echo off
setlocal ENABLEEXTENSIONS
if .%1==. (
set TEST1=NOTFOUND
) else (
set TEST1=%1
)
echo %TEST1%
I'm sure this has to do with variable expansion but it really threw me. It looks like I will assign another variable in the if statement like set USING_DEFAULT_TEST1=TRUE and set USING_DEFAULT_TEST1=FALSE or compare to the default value or something.

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.
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.

Related

Why does echo use a different value of loop-variable than set?

I am attempting to use a simple for-loop to create a series of strings followed by a number. However, when I try to add the iterator-variable to the end of the strings, the value which is added is instead the endpoint of the loop.
A simplified version of the script is:
#echo off
set STARTSTRING="Test-
for /l %%x in (1, 1, 26) do (
echo %%x
set CONCATENATED_STRING=%STARTSTRING%%%x"
echo %CONCATENATED_STRING%
)
pause
Which gives the output:
Can someone please explain to me where I am going wrong?
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 - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
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.
So -
#echo off
setlocal enabledelayedexpansion
set "STARTSTRING=Test-"
for /l %%x in (1, 1, 26) do (
echo %%x
set "CONCATENATED_STRING=!STARTSTRING!%%x"
echo !CONCATENATED_STRING!
)
pause
Note the positioning of the quotes (so that stray spaces on the ends of lines are not allocated to the variable.)
!var! accesses the run-time value of the variable in delayedexpansion mode - %var% the parse-time value
The setlocal also ensures that when the batch ends, any changes made to the environment variables is discarded. Withou it, as you have seen, the variable retains its value as set on the previous batch-run.

Loop through files with .jar extension [Batch]

So, I've got a bit of code, which would in theory work, but it doesn't..
It loops through some files, but not all, but not the specific file type I want it to loop through (.jar)
I got:
for /r %%f in (*.jar) do (
ECHO path=%%~pf
ECHO filename=%%~nf
ECHO fileextension=%%~xf
SET fileextension=%%~xi
IF "%fileextension%" == ".jar" (
call proc %%f
)
)
change
IF "%fileextension%" == ".jar" (
to
IF /i "%%~xf" == ".jar" (
Three problems:
1) the filename is in %%f, not %%i
2) 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 - the same thing applies to a FOR ... DO (block).
Within a block statement (a parenthesised series of statements), REM statements rather than the broken-label remark form (:: comment) should be used because labels terminate blocks, confusing cmd.
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
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.
2) you'd probably need if /i to make the if statement case-insensitive, for the situation where the file extension matches but is a different case.

set statements don't appear to work in my batch file

I've found what appears to be an explanation of my problem here
DOS batch: Why are my set commands resulting in nothing getting stored?
but I don't really understand the explanation.
Here is my script...
for /R /d %%f in (\Product\Database\SQL\Project\Model\Scripts\*) DO (
REM echo %%f
SET !LOAD_FILE_FILTER= %%f\*.sql
echo file: %!LOAD_FILE_FILTER%
CALL %!BATCH_FILE% -u %!USER_NAME% -p %!PASSWORD% -s %!SERVER_NAME% -d %!DATABASE_NAME% -f %!LOAD_FILE_FILTER% -o %!LOG_FILE%
IF %!EchoErrors%==1 (
ECHO [
TYPE %!LOG_FILE%
ECHO ]
)
)
The echo always prints file: *.sql and the script I pass this var to always complains LOAD_FILE_FILTER is empty.
I have tried adding setlocal EnableDelayedExpansion as suggested in the article but it doesn't solve the problem. The echo file: %!LOAD_FILE_FILTER% always prints the last subdirectory in the directory I'm running from. The echo %%f always prints the correct value.
What does the '!' behind the variable do for/to me?
On a side note, could someone explain to me the difference between
SET !VAR
and
SET VAR
%VAR&
&!VAR&
!VAR!
%%VAR
We are going to start with a simple case
set "var="
set "var=test"
echo %var%
Reading the code, it removes the content of the variable, assigns it a new value and echoes it.
Let's change it a bit concatenating the last two commands
set "var="
set "var=test" & echo %var%
"Same" code, but in this case the output to console will not show the value in the variable.
Why? In batch files, lines to execute are parsed and then executed. During the parse phase, every variable read operation (where you retrieve the value of the variable) is replaced with the value stored inside the variable at parse time. Once this is done, the resulting command is executed. So, in the previous sample when the second line is parsed, it is converted to
set "var=test" & echo
now, there are no read operations on the line and no value to echo, as when the line was readed the variable didn't hold any value (it will be assigned when the line is executed) so the read operation has been replaced with nothing. At this point, the code is executed and the perceived behaviour is that the set command failed as we don't get the "obvious" value echoed to console.
This behaviour is also found in blocks. A block is a set of lines enclosed in parenthesis (usually for and if constructs) and are handled by the parser as if all the lines in the block are only one line with concatenated commands. The full block is readed, all variable read operations removed and replaced with the value inside the variables, and then the full block, with no variable references inside is executed.
At execution time there are no read operation on variables inside the block, only its initial values, so, any value assigned to a variable inside the block can not be retrieved inside the same block, as there isn't any read operation.
So, in this code
set "test=before"
if defined test (
set "test=after"
echo %test%
)
after the first set is executed, the block (the if command and all the code enclosed in its parenthesis) will be parsed and converted into
if defined test (
set "test=after"
echo before
)
showing the "wrong" value.
The usual way to deal with it is to use delayed expansion. It will allow you to change, where needed, the syntax to read the variable from %var% into !var!, indicating to the parser that the read operation must not be removed at parse time, but delayed until the command is executed.
setlocal enabledelayedexpansion
set "var="
set "var=test" & echo !var!
The now third line is converted at parse time to
set "var=test" & echo !var!
yes, the variable reference is not removed. The read operation is delayed until the echo command will be executed, when the value of the variable has been changed.
So
%var% is a variable reference that will be replaced at parse time
!var! is a variable reference that will be replaced at execution time
%x with x a single character is usually a for replaceable parameter, a variable that will hold the current element being interated. By its own nature, will be expanded at execution time. The syntax with a single percent sign is used at command line. Inside batch files the percent sign need to be escaped and the syntax to refer to the replaceable parameters is %%x

Command Line: Using a for loop to create directories with increasing names

I am trying to write a batch file that does the following:
Prompt user for the directory to create the new folder newest
Prompt user for an integer limit
Create directory newest
CD newest
FOR loop for limit iterations
Create directory "Month " + iteration
For example:
newest = Reports
limit = 12
I should end up with:
\Reports\Month 1
\Reports\Month 2
\Reports\Month 3
...
\Reports\Month 12
This is my code so far:
setlocal enabledelayedexpansion
FOR /L %%i IN (1,1,%limit%) DO (
set "month_counter=Month %%i"
echo %month_counter%
MD %month_counter%
)
endlocal
If I set limit = 12, I get 12 error messages stating:
Echo is off.
The syntax of the command is incorrect.
I appreciate the help.
FOR /L %%i IN (1,1,%limit%) DO (
MD "Month %%i"
)
You have the standard delayed expansion problem - hundreds of articles on SO about this.
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 - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
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.
Note therefore the use of CALL ECHO %%var%% which displays the changed value of var. CALL ECHO %%errorlevel%% displays, but sadly then RESETS errorlevel.
So - you could use
set "month_counter=Month %%i"
CALL echo %%month_counter%%
If you really, really want to - or one of the other techniques, but it's far easier to simply make the directory from your constant data + the iteration counter in %%i as shown.

Batch File EnableDelayedExpansion and Variable Setting

I'm editing a batch file given to me and I'm not sure what the following line of code does:
set allKeys=%allKeys% !currentKey!
thanks!
It appends the run-time value of the variable currentkey after a space to the parse-time value of allkeys and assigns the result as the run-time value of allkeys - provided delayedexpansion is invoked. If delayedexpansion is not invoked, it appends the string !currentKey!, not the value of the variable currentkey.
Without any context information, we're guessing beyond that...
Here is an example in code to try.
allkeys is set outside the for in do loop.
Inside the for in do loop it is changed but as it uses %allkeys% the changes are not cumulative.
#echo off
setlocal enabledelayedexpansion
set allkeys=one
for %%a in (two three four five six) do (
set currentkey=%%a
set allKeys=%allKeys% !currentKey!
echo allkeys is now "!allkeys!"
)
echo allkeys is now "%allkeys%" outside the loop
pause
Change this line
set allKeys=%allKeys% !currentKey!
to this and run it to see the difference.
set allKeys=!allKeys! !currentKey!
For a decription of what delayed expansion is, type SET /?.

Resources