Why are environment variables not updating within FOR loop? - batch-file

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

Related

Why is setting a batch script variable delayed?

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.

Batch script to display variable content in mail body

I have batch script which displays the variable content (%Build%) in a mail body.
for example if the Varaible content is having:
%Build%= aa001.skc
the mail which is triggered by the script has the mail body as below (OUTPUT).
"Skip file generated for the files aa001.skc"
(Imagine test.log contains the value aa001.skc in it)
below is my current script for the same:
#echo off
for /f "delims=" %%x in (C:\Users\Desktop\test.log) do (
set Build=%%x
echo %Build%
)
"blat.exe" -to "abc#gmail.com" -cc "abc#gmail.com","xyz#gmail.com" -f 123#test.com -body "Skip file generated for the files %Build%" -subject "Skip file found" -server mailout.xyz.com
exit
But if the test.log contains values as
aa001.skc
aa002.skc
aa003.skc
with the above script my output is showing only as below in the mail body.
Current output:
"Skip file generated for the files aa003.skc" (Its taking the last value, not sure how to put it in for loop in mail body)
But my OUTPUT of the mail body should be like below.
"Skip file generated for the files
aa001.skc
aa002.skc
aa003.skc"
Kindly help me achieve the same.
It's normal for batch files to commence with a setlocal instruction which effectively ensures that the environment remains unchanged at the end of the batch, so running successive batches does not accumulate changes to contaminate for further batch executions.
To fix your process - version 1
#echo off
setlocal
for /f "delims=" %%x in (C:\Users\Desktop\test.log) do (
call set "Build=%%x %%build%%"
call echo %%Build%%
)
blat ......
(in this one, the setlocal is not strictly necessary - I'm assuming it's being called from another batch)
[EDIT: to add newlines]
Poorly-documented featue of BLAT is that a pipe character (|) in the body is transformed to a newline, hence
#echo off
setlocal
set "build=|"
for /f "delims=" %%x in (C:\Users\Desktop\test.log) do (
call set "Build=%%x|%%build%%"
call echo %%Build%%
)
blat ......
You may also wish to insert a pipe just after %build% in the -body parameter.
[end-edit]
Another common way is to use delayedexpansion
#echo off
setlocal enabledelayedexpansion
for /f "delims=" %%x in (C:\Users\Desktop\test.log) do (
set "Build=%%x !build!"
echo !Build!
)
blat ......
(setlocal enabledelayedexpansionrequired, notice thecall`s have gone.
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 - actually something you didn't say. Your code should have shown the original value of build three times. Now - if this had been retained from an earlier run, it would have shown the last value three times. If on the other hand you had a clean environment, then since build would not have been set, you should have seen echo is off three times.
See endless SO items on delayedexpansion for more info.

In a Batch File: How to echo each variable assigned from lines in a text file that are in a For loop?

I am trying to write a batch file that reads each line in a text file and assigns it (each line) to a variable.
I am using the example found here: Read each line in a txt file and assign variables using windows dos commands, which is:
SETLOCAL EnableDelayedExpansion
SET count=1
FOR /F "tokens=* delims= usebackq" %%x IN ("%TEXT_T%") DO (
SET var!count!=%%x
SET /a count=!count!+1
)
ENDLOCAL
Now, all I want to do is echo the results of each variable, but I just can't seem to be able to do so. Below is what I have, which gives me the results for var0 three times--my text file has three lines. I changed the start of count from the above code from 1 to 0.
#echo off
SETLOCAL EnableDelayedExpansion
SET count=0
FOR /F "tokens=* delims= usebackq" %%x IN ("afile.txt") DO (
SET var!count!=%%x
echo !var%count%!
SET /a count=!count!+1
echo !count!
)
ENDLOCAL
While this code
echo !var%count%!
seems logic, as count is changed inside the loop, you can not access the value of the variable without delayed expansion. But
echo !var!count!!
^...^ ^^
first variable second "variable"
will not work, as the parser is not able to properly determine where each variable reference start/end
You can use any of the two following lines
for %%a in (!count!) do echo !var%%a!
call echo %%var!count!%%
How does it work?
The original idea is good, but there are limits in the syntax. We need delayed expansion over the var.. variable, but also over the count.
1.- In the first solution, we store the delayed expansion of the count variable inside the for replaceable parameter, that is used instead of the count variable in the echo command, keeping the delayed expansion only around the var variable.
!var!count!! => %%a=!count! => !var%%a!
2.- In the second solution, a call command is used. This call causes a double evaluation of the line, first on the line parse to execute the call, second while call execution. That is, the line
call echo %%var!count!%%
is first parsed, and the only variable referenced in it is !count! (a double percent sign is a single escaped percent sign), so the line is translated into (suppose count contains 5)
call echo %var5%
Now, the call is executed, line is parsed again to execute the echo and the value of the correct variable is retrieved.

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.

Variable assignment problem in DOS batch file for loop

I have a variable assignment problem inside the DOS script for loop. It never assigns the value, its always blank. Below the sample code
#echo off
set ans=%1
SET STRING=%ans:"=%
echo Parsing the string "%STRING%":
for /f "tokens=1-2" %%a in ("%STRING%") do (
set Word1 =%%a
echo Word 1: %%a
echo Word 1: %Word1%
set Word2 =%%b
if %%b.==. (Set server =\\.\pipe\mssql$microsoft##ssee\sql\query ) else (Set server =%%b)
)
echo Server name "%server%"
sqlcmd -s %server%
value of %%a is not assigned to variable Word1. But when i echo the %%a, it shows the correct value. As well in the last empty value check if condition, the server variable never set. I am very confused here. can someone help me out??
P.S: input to the script is any 2 word string (ex: a.bat "l dev-server")
You need to use delayed expansion - !Word1! instead of %Word1%.
By default, when the shell first reads the statement, all variables are substituted with their current values, and the modified statement is used every time that line is hit. This is simply how DOS works, and it's kept that way in the Windows shell for backwards compatibility.
Delayed expansion, on the other hand, reinserts the value every time the statement is hit. That will give you the desired result.

Resources