Why is setting a batch script variable delayed? - batch-file

How do I solve delayed variable assignment?
(I'm making the term delayed variable assignment up, because it seems like a reasonable descriptive phrase).
Example Batch Script Code script.bat:
#echo off
set mylocation=%CD%
echo mylocation %mylocation%
echo CD %CD%
Actual Output
C:> script.bat
mylocation
CD C:\
C:>
What I want it to do (or thought it would do)
C:> script.bat
mylocation C:\
CD C:\
C:>
Edit (changed): If I echo %mylocation% in command prompt after the script ends it has a value.
C:>echo %mylocation%
C:\
C:>
Edit: This is the original Code, and you can view the youtube video https://youtu.be/jQzEFD3yISA - where I try to show as much detail as possible so that, everything is exact.
#echo off
set natasha_command=%1
if %natasha_command% == start (
set mylocation=%CD%
echo mylocation %mylocation%
echo CD %CD%
)
goto :eof

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.

Related

Why are environment variables not updating within FOR loop?

I have a batch file that is scanning a file URLs.txt and for each url run it and download the file. The issue I have is the environment variable within the FOR loop. I am using cat, sed and awk to get the last two parts of the the url so I can provide the filename. The issue is the environment variable is never updated after the first run. I can see that tmp2.txt just updated correctly for every url, but the batch file is not updating outfile and thus I keep overwriting the first file.
I tried to simplify the batch file for a test and any variable within a for loop never seems to update.
#echo off
setlocal enabledelayedexpansion
for /F "tokens=*" %%A in (URLs.txt) do (
echo %%A > tmp.txt
cat tmp.txt | sed "s/\(.*\)\//\1_/" | awk -F "/" "{print $NF}" > tmp2.txt
set /p outfile=<tmp2.txt
echo Varible A
echo %%A
echo.
echo Varible outfile
echo %outfile%
call curl.exe -o %outfile% -u username:password --insecure %%A
pause
)
Why is environment variable outfile not updated within FOR loop?
echo !outfile!
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.
In your case, since outfile is assigned the value %%A, you can replace %outfile% with %%A - and it would be an idea to "quote the string" anyway since "quoting a string" makes it a single token rather than a series - just in case your filenames contain separators like Space

ECHO strange behavior when inside IF block

I have a Windows Batch script named test.bat as follows:
#ECHO OFF
SETLOCAL
SET name=Dan
SET greeting=Hi %name%, how are you?
ECHO %greeting%
When executed I get the following output:
Hi Dan, how are you?
This is what I would expected. I changed the script to the following:
#ECHO OFF
SETLOCAL
IF EXIST test.bat (
SET name=Dan
SET greeting=Hi %name%, how are you
ECHO %greeting%
) ELSE (
ECHO Nofile
)
I would expect to get the same output. Instead I get the following:
ECHO is off.
Please can someone help me understand why.
you need delayed expansion:
#ECHO OFF
SETLOCAL enableDelayedExpansion
IF EXIST test.bat (
SET name=Dan
SET greeting=Hi !name!, how are you
ECHO !greeting!
) ELSE (
ECHO Nofile
)
The batch files have two phases of reading the script - execution and parsing. During parsing phase all variables enclosed with % are substituted and during execution phase the commands are executed. With the delayed expansion turned on the variables enclosed with ! will be expanded during execution phase (i.e. later)
Brackets on the other side (as well as &) puts the commands in a block taken as one single command so all variables with % are substituted during the first phase and when you are setting variables you'll need delayed expansion.

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.

set fileName and echo to cmd.exe in for loop of batch?

I'd like to put each of the many properties' file names into variable fileName and echo them out to the command prompt window. But only the last properties file name to be cycled thru is printed out as many times as there are properties files. Is there an easy fix to this problem. I know that ...DO echo %%-nxG can do the same thing but I'd like to save the file name in %%~nxG for future use.
FOR %%G IN (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
set fileName=%%~nxG
echo %fileName%
)
You need to use delayed expansion:
setlocal enabledelayedexpansion
FOR %%G IN (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
set fileName=%%~nxG
echo !fileName!
)
Environment variables in cmd are expanded when a command is parsed – in this case this includes the whole block in parentheses. So %fileName% gets replaced by an empty string because it didn't have a value before the loop ran. Delayed expansion uses ! instead of % and changes variable evaluation so that they are evaluated just before a command is run.
help set has more details about why and when it is necessary. In general, whenever you modify and use a variable within a loop you have to use delayed expansion, but it comes with a few other benefits too.

Batch File to Search for a String within Directory Names

My batch file terminates prematurely after I assign the first environmental variable (script output below). I've tried turning echo on, using errorlevels, sending the output to a text file, and checking syntax. I've spent several hours researching debugging batch scripts, but I have finally hit a brick wall.
Script's Goal: Search each directory name of the user's Program Files, looking for common antivirus programs. I realize that it would be easiest iterate through an array of antivirus names for this purpose, but I want to keep it simple for now.
#echo off
::variables
set AntiVirus1="Initial Value"
IF NOT ERRORLEVEL 0 echo %ERRORLEVEL%
else echo "env. variable created successfully."
for /d %%f in (""%ProgramFiles%\*"") do (
{
IF NOT ERRORLEVEL 0 echo %ERRORLEVEL%
echo "%%f"
if exist /i "*McAfee*" < %%f %AntiVirus1%="McAfee"
::find "Norton" < %%f
::find "Comodo" < %%f
::find "AVG" < %%f
}
echo %AntiVirus1%
#pause
Output of this script:
C:\Users\Matt\Desktop>set AntiVirus1="Initial Value"
C:\Users\Matt\Desktop>
Can someone point me to what I'm doing wrong?
UPDATE Corrected script, now working but returning incorrect results:
::#echo off
::variables
set AntiVirus1="Initial Value"
IF NOT ERRORLEVEL 0 (echo %ERRORLEVEL%) ELSE echo "env. variable created successfully."
echo Checking Program Files...
for /d %%f in ("%ProgramFiles%\*") do (
echo "%%f"
if %%f=="*adobe*" set AntiVirus1="adobe"
)
echo %AntiVirus1% found
#pause
First of all, ELSE must be on the same line with IF or on the same line with the closing parenthesis that pertains to IF. In you particular case you should change your first IF...ELSE command like this:
IF NOT ERRORLEVEL 0 (ECHO %ERRORLEVEL%) ELSE ECHO "env. variable created successfully."
or like this:
IF NOT ERRORLEVEL 0 (
ECHO %ERRORLEVEL%
) ELSE ECHO "env. variable created successfully."
(Capitalisation and indentation are perfectly optional.)
Other issues:
Duplicated quotation marks in the FOR loop header:
for /d %%f in (""%ProgramFiles%\*"") do (
should be
for /d %%f in ("%ProgramFiles%\*") do (
Braces ({, }) around the loop body. They are not part of the loop syntax (in fact, they are not part of batch scripting syntax at all), so should be dropped.
No closing parenthesis matching the opening one after DO. It should be added on a separate line after the loop body.
Incorrect use of ::-style comments in the loop body. They are not allowed inside bracketed blocks. Use REM instead.
UPDATE
In batch scripting, testing for a substring is done somewhat unusually. You'll need another environment variable and you'll also need to enable delayed expansion. The latter is not really connected with the comparison, but it is needed because the comparison is going to be performed within a bracketed block.
Here's your new script modified, with the changes highlighted:
::#echo off
::variables
set AntiVirus1="Initial Value"
IF NOT ERRORLEVEL 0 (echo %ERRORLEVEL%) ELSE echo "env. variable created successfully."
SETLOCAL EnableDelayedExpansion
echo Checking Program Files...
for /d %%f in ("%ProgramFiles%\*") do (
echo "%%f"
SET "folder=%%f"
if /I NOT "!folder:adobe=!"=="!folder!" set AntiVirus1="adobe"
)
echo %AntiVirus1% found
#pause
Here's a bit of explanation.
The ! syntax is a delayed expansion equivalent of % and is used with environment variables only, not with loop variables and not with command line parameters. Delayed expansion is needed because we are in a bracketed block. A bracketed block is parsed entirely before it starts executing, so all %var% expressions are expanded (evaluated) before the block starts and are not changed throughout the block's execution. That cannot suit us because we need to assign different values to a variable during the block's execution, and the values must be read within the block. Delayed expansion, as follows from the name, delays the expansion of a variable until the actual execution of every single command that references that variable. Because immediate expansion can still be used alongside delayed expansion, a different syntax is introduced, which is ! around variable names, instead of %.
!folder:adobe=! means evaluate folder replacing every occurrence of adobe with an empty string. The result of this expression is then compared to the (unchanged) value of folder. If there's a match, then the replacement didn't occur, which means there was no adobe in the value of folder in the first place. In this case we should do nothing. But if there was not a match, i.e. if the modified value didn't match the unmodified one, then we should set the AntiVirus1 variable. This is why there's NOT in front of the comparison.
The /I option simply means case-insensitive comparison.

Resources