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

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.

Related

CMD: piping ECHO to SET/ expanding variables in variables

%x:~12,3% Returns 3 characters starting at the 12:th character in x variable.
What I have been trying to accomplish is using variables instead of 12 and 3.
Let's say y=12 and z=3.
Then, you can't use %x:~%y%,%z%%, because CMD will think %x:~% is a variable.
What you can do is set var=%%x:~%y%,%z%%%. This will expand the inside variables y and z, but not x, so that the value of var is %x:~12,3%.
The remaining task at hand now is to finally expand %x:~12,3%. I have been trying to append echo in the beginning so that var=echo %x:~12,3%.
If at the commandline or in a batch file you now use %var%, this should execute the echo command, and expand the succeeding expression, but it doesnt, instead echo %x:~12,3% results in simply %x:~12,3% being printed to the screen, unexpanded.
I was thinking that maybe if you set var to %x:~12,3%, then echo it
and pipe the output into another ECHO command or SET command that the expression would be expanded, but it seems that ECHOand SETdoesn't accept data being piped into it at all?
How can I make this work?
I copied the entire text below from this answer; I just changed the names of variables and particular examples to match the ones of this question:
%x:~12,3% returns 3 characters starting at the 12:th character in x
variable. What I have been trying to accomplish is using variables
instead of 12 and 3. Let's say y=12 and z=3.
If you want to use another variables for substring position and lenght, then you must know that the replacement of variables enclosed in percents by their values is parsed from left to right; this mean that: %x:~%y%,%z%% don't give the desired result because it mean: show the value of x:~ variable, followed by y, followed by the value of , variable, etc.
To solve this problem you must use Delayed Expansion, that is, insert setlocal EnableDelayedExpansion command at beginning, enclose substring variables in percents, and enclose the original variable in exclamation marks:
setlocal EnableDelayedExpansion
set x=0123456789ABCDEF
set y=12
set z=3
set var=!x:~%y%,%z%!
You may also use parameters of FOR commands as indexes: for /F "tokens=1,2" %%i in ("%y% %z%") do set var=!x:~%%i,%%j!.
To get the value of a substring when the index change inside FOR/IF enclose the variable in double percents and precede the command with call. For example, to show a substring at a random y position between 0 and 12 and lenght z:
if %some% == %test% (
set /A y=!random! %% 13
call echo %%x:~!y!,%z%%%
)
You may also use this method outside parentheses in order to avoid the Delayed Expansion:
call echo %%x:~%y%,%z%%%
Another way to achieve previous process is using an additional FOR command to change the delayed expansion of the index by an equivalent replaceable parameter, and then use the delayed expansion for the original variable. This method run faster than previous CALL:
if %some% == %test% (
set /A y=!random! %% 13
for %%y in (!y!) do echo !x:~%%y,%z%!
)
You need to enable delayed expansion.
#echo off
setlocal enabledelayedexpansion
set string=1234567890abcdef
set substring_start=12
set substring_length=3
set substring=!string:~%substring_start%, %substring_length%!
set command=echo !substring!
!command!
pause
#ECHO OFF
SETLOCAL
SET "x=abcdefghijklmmopqrstuvwxyz"
SET /a start=12
SET /a length=3
CALL SET "var=%%x:~%start%,%length%%%"
ECHO var=%var%
CALL echo %%x:~%start%,%length%%%
SETLOCAL ENABLEDELAYEDEXPANSION
SET /a start=6
SET /a length=4
SET "var=!x:~%start%,%length%!"
ECHO var=%var%
echo !x:~%start%,%length%!
GOTO :EOF
Two methods - the first in standard mode and the second using delayedexpansion. There are hundreds of examples on SO about delayedexpansion.

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.

Batch script error: "/ was unexpected at this time."

I wrote a little script to display every line of a text file with a random amount of pause between each of them:
SET pauseTime=10
SET maxval=20
SET minval=5
FOR /f %%j in (search1.txt) DO (
SET pauseTime=%RANDOM% * (%maxval% - %minval% + 1) / 32768 + %minval%
ECHO.%pauseTime%
TIMEOUT %pauseTime%
ECHO.%%j
)
Running this in cmd.exe gives me:
C:\Users\Tim\Desktop>SET minval=5
/ was unexpected at this time.
However, if I simply do:
FOR /f %%j in (search1.txt) DO (
ECHO.%%j
)
I get all the lines printed with no errors
What's going on?
You need to add the /A switch to your SET statement in order to do math operations. (I usually also have to remove any embedded spaces to get it to work properly as well.)
SET /A pausetime=%RANDOM%*(%maxval%)-%minval%)/32768+%minval%
The error you're getting is also caused by the batch processor not properly handing the parentheses within the for expression (the nested pair in the numeric expression). You can fix that by breaking that part of the expression out to a separate variable, and then using that variable in place of the portion within the parentheses:
SET pauseTime=10
SET maxval=20
SET minval=5
SET /A maxmin=%maxval% - %minval% + 1
FOR /f %%j in (search1.txt) DO (
SET /A pauseTime=%RANDOM% * %maxmin% / 32768 + %minval%
ECHO. %pauseTime%
TIMEOUT %pauseTime%
ECHO. %j%
)
the issue here is because you use nested ( ) brackets which batch can't handle and causes your issue, you might need to split your pausetime calculation into 2 lines.
see this article
The CMD shell statement does not use any great intelligence when evaluating parenthesis, so for example the command below will fail:
IF EXIST MyFile.txt (ECHO Some(more)Potatoes)
As mentioned within the other answers, the problem is caused by the nested parenthesis.
Instead of avoiding them, you could also escape them by preceding ^ characters -- this works:
IF EXIST MyFile.txt (ECHO Some^(More^)Potatoes)
Without the caret ^ here, the command line interpreter takes the first ) to close the first (.
However, applying the above escape technique to the code of the original question avoids the error message, but does not result in a working script, even if the inner SET is replaced by SET /a (as mentioned by #KenWhite), because you need to establish delayed environment variable expansion; otherwise, the displayed pauseTime value is always the initial one, 10. The following fix works:
#ECHO OFF
SET pauseTime=10
SET maxval=20
SET minval=5
SETLOCAL EnableDelayedExpansion
FOR /f %%j in (search1.txt) DO (
SET /a pauseTime=!RANDOM!*^(maxval-minval+1^)/32768+minval
ECHO.!pauseTime!
TIMEOUT !pauseTime!
ECHO.%%j
)
ENDLOCAL
Herein, the SETLOCAL statement enables the delayed expansion before the FOR command executes. In the body of the FOR loop, %pauseTime% and %RANDOM% are replaced by !pauseTime! and !RANDOM!, respectively; those are the variables subject to delayed expansion (because of the enclosing !!), meaning that they are not (as usual) expanded immediately when the entire FOR command is parsed, but when it is executed. For the sake of legibility, I removed the %% from the other variables in the SET /a command since they are not required when the /a switch is present (note that there is also a % operator supported!!).
Note: After this script has run, pauseTime is reset to the initial value 10 as the SETLOCAL/ENDLOCAL block constitutes a new localised environment namespace; if you need the last value, replace line ENDLOCAL by ENDLOCAL & SET pauseTime=%pauseTime%; this utilises standard immediate expansion...
Nevertheless, since minval and maxval are constants in the script it is most intelligent to do the calculation maxval-minval+1 outside of FOR once only, like #KenWhite suggested.

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 if statement weirdness with variable assignment and expansion

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.

Resources