Delayed expansion doesn't work inside delayed expansion - batch-file

I've got a script that does everything I expect it to do, apart from one line.
I've done similar before, but I can't get this one to work.
The code I've got is here
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
::Set Path to be folder of Sage Files
SET PATH=C:\Welcome\Progs\SitesDataSetups\GeorgeYarmouth
::set date variables
for /f "tokens=1" %%i in ('date /t') do set thedate=%%i
set mm=%thedate:~3,2%
set dd=%thedate:~0,2%
set yyyy=%thedate:~6,4%
::Set T_DAY variable to date in ddmmyy format
set T_DAY=%dd%%mm%%yyyy:~2%
c:
cd\
cd %path%
for /f "usebackq tokens=* delims= " %%P in (`dir sage*.csv /od /b`) do (
set SAGE=%%P
set SAGE2=!SAGE:~0,8!_EDITED
set EODNUM=!SAGE:~4,4!
for /f "tokens=* delims= " %%A in (%%P) do (
echo %EODNUM%
set S=%%A
***This line is the problem***
set S=!S:%T_DAY%=%EODNUM%!
echo.!S! >> %PATH%\TEST\!SAGE2!.csv
)
)
endlocal
I was expecting that is would take each line of the csv file and replace it with itself, except with a string replace of the current date with the variable EODNUM (which it does... only the variable is expanded before it is set, so is nothing)... The delayed expansion should solve this, but I can use this line of code
set S=!S:%T_DAY%=!EODNUM!!
because I think its too many !'s for CMD.
Am I missing something, or is there a better way to code this?? (I'm not a programmer of any kind, and most of the code I write comes from trial and error, and 'borrowing' from other scripts, so this may be a very messy way to do this).

Transfer the the value of !EODNUM! to a FOR variable, and then use your FOR variable as the replace string.
echo !EODNUM!
set "S=%%A"
for /f "delims=" %%E in ("!EODNUM!") do set "S=!S:%T_DAY%=%%E!"
echo.!S!>> %PATH%\TEST\!SAGE2!.csv

By way of explanation...
CMD reads (and does env var substitution), parses, and executes one top-level command at a time.
In your example, it reads the "for /f..." command all at once parsing and performing %var% substitution.
Once this is complete, it then executes the for loop, performing delayed !var! substitution.
Unfortunately, !var! substitution is not a do-substitution-until-none-left. This makes it hard (as in the answerer's solution) to perform the substitution into the !var:src=dst! value.
You will need a way that during execution you can get guaranteed substitution. This requires a for-statement, or something that involves reading and %var% substituting again. One way of doing this is to use the CALL :LABEL form where you can call to a specific label in your .cmd file and have this section do what you want:
...
call :GenS
...
and then:
:GenS
set S=!S:%T_DAY%=%EODNUM%!
goto :eof
BTW: I'm perplexed that you didn't notice the ECHO %EODNUM% not working in the loop as during the reading of the for loop all %var% substitutions are made.

Related

Use a for loop variable to call a predefined variable

I'm looking to use a variable which gets defined inside a for loop and use it to call a predefined variable.
For example: To start the program "Slack" I need to call the variable %Slack%.
The batch file will take your input, so If I input "Slack", the variable !filename[%%i]! will be assigned "Slack". Once that is done, I wish to call %Slack% using !filename[%%i]!. As per logic: %!filename[%%i]!%
Using %!filename[%%i]!% will make the batch file crash and close.
Setting set file=!filename[%%i]! and running it with %file% will return nothing.
Any smart trick?
#echo off
setlocal EnableDelayedExpansion
:: Predefined variables
for /f "delims== tokens=1,2" %%G in (D:\programs.txt) do set %%G=%%H
set /P "filenames=Enter programs to install: "
:: Puts input into array
set n=0
for %%a in (%filenames%) do (
set /A n+=1
set "filename[!n!]=%%~a"
)
:: Run variables imported from programs.txt
for /L %%i in (1,1,%n%) do (
:: This is where I want !filename[%%i]! to be called as %!filename[%%i]!%
)
pause
endlocal
program.txt example
Slack=C:\Users\username\AppData\Local\slack\slack.exe
Text=C:\textfile.txt
Program=C:\program.bat & C:\program_license.txt
I don't like to use the CALL hack described in the comments because 1), it is relatively slow, and 2) it causes an extra round of parsing that can require additional escaping (not an issue here). Even worse, it will corrupt quoted ^ - "^" becomes "^^", and there is no escape sequence that can prevent that.
My solution is to add an additional FOR loop so that the expansion can be staged.
for /L %%i in (1,1,%n%) do for %%A in ("!filename[%%i]!") do echo !%%~A!
This is certainly more verbose than the CALL hack, but it is a good code pattern to learn because it is more universally applicable.

Not able to print the variable in windows batch file that stores an extracted value from CSV file

Below is my code and i am not able to print the variable in windows batch file that stores an extracted value from CSV file
#echo off
Set _InputFile=D:\TH_Scripts\InputParamTest.csv
for /F "usebackq tokens=* delims=" %%A in (%_InputFile%) do (
set the_line=%%A
goto process_line
)
:process_line
echo i am here
pause
for /F "usebackq tokens=1,2,3,4,5,6,7 delims=[,]" %%1 in (%the_line%) do (
set hexcode=%%1
set country=%%2
set reg=%%3
set owner=%%4
set callsign=%%5
set planetype=%%6
set model=%%7
set THISLINE=%hexcode%,%country%,%reg%,%owner%,%callsign%,%planetype%,%model%
echo %THISLINE% > %THEOUTPUTFILE%
pause
)
for /F "usebackq tokens=1,2,3,4,5,6,7 delims=[,]" %%1 in (%the_line%) do (
%%number is not supported by batch syntax. %n means "the nth parameter to the procedure".
change %%1 to %%a. The variables assigned are then %%a, %%b etc. to %%g note case is important. %%a can be %%i if you like - the values extracted are then assigned to %%i..%%o
2.
You need to search for innumerable articles on delayed expansion on SO. The value of %var% within a code block (parenthesised series of instructions) will be the value that the variable had when the code block was encountered - not the value as it is varied in the block (the "run-time" value)
To extract the run-time value, you need to first invoke delayedexpansion mode by executing a setlocal enabledelayedexpansion instruction (usually done directly after the initial #echo off) and then access the run-time value of the variable by using !var!.
That having been said, unless you are actually using the variables you are establishing in the block, you can directly output your list using %%a, etc, not %hexcode% or !hexcode!. Without further information about how you intend to use these variables elsewhere, this may or may not be useful in your case.
BTW - 1,2,3... is not incorrect syntax, but 1-7 is shorter and means the same in this context.

Storing variables name using for loop [duplicate]

This question already has answers here:
windows batch files: setting variable in for loop
(3 answers)
Closed 7 years ago.
I want to get the list of all files inside of c:\test. My attempt is this:
set loc=c:\test
for /f %%i in(dir "%loc%" /b') do (
#set variable=%%i
echo %variable%
)
...but I'm getting back only one file name, n times. How to get all the files in that folder.
1. The reason you get back only 1 file name all the time is that you did not Setlocal EnableDelayedExpansion.
2. Check again you code, you did not add a single quotation mark before dir "%loc%" /b'.
3. Check again your code once more, you can't stick "in" and "(" like in(, this will absolutely ruin your code.
#echo off
Setlocal EnableDelayedExpansion
set "loc=c:\test"
for /f %%i in ('dir "%loc%" /b') do (
set "variable=%%i"
echo !variable!
)
You need to enable "delayed expansion" for this to work. If it isn't enabled, variables are evaluated exactly once, when the script is parsed. This is why you get the one filename n times.
Some notes:
Enable delayed expansion with SETLOCAL EnableDelayedExpansion
When delayed expansion is enabled, to take advantage of it, you need to use ! instead of % as variable delimiter, so your %variable% becomes !variable!
Loop variables like your %%i are an exception in that they will change their value even when delayed expansion is not enabled. Try ECHOing %%i in your original script (i.e. without SETLOCAL EnableDelayedExpansion) to see what I mean
Edit: dark fang correctly points out syntax errors in your script, which I didn't even catch - but from the behaviour your described, these were not in your script when you were trying run it, because it would just have errored out.
In the end you get:
SETLOCAL EnableDelayedExpansion
set loc=c:\test
for /f %%i in ('dir "%loc%" /b') do (
#set variable=%%i
echo !variable!
)

batch %var:*\% to eliminate all before last \ not first

set var=C:\Users\user\Desktop\bla\bla.exe
set var=%var:*\%
echo %var%
this returns Users\user\Desktop\bla\bla.exe - is there any way to make it focus on the last \ and not the first one so that it would just return bla.exe? bear in mind that this will be used on multiple files and folders so i won't always know how many sub-folders there are.
#ECHO OFF
SETLOCAL
set "var=C:\Users\user\Desktop\blah blah\bla.exe"
FOR %%a IN ("%var%") DO (
SET "filename=%%~nxa"
FOR %%b in ("%%~dpa.") DO SET "lastleaf=%%~nxb"
)
ECHO filename is "%filename%"
ECHO lastleaf is "%lastleaf%"
GOTO :EOF
Normally, the next question is about how to obtain the last leaf of the directory-tree. No subroutines required...
Note positioning of quotes to minimise problems with separators. ALso minor directory name-change to exhibit differences.
The following snippet shows one way to do it.
#setlocal enableextensions enabledelayedexpansion
#echo off
:main
set var=C:\Users\user\Desktop\blah blah\yada yada.exe
call :basename result "%var%"
echo %result%
endlocal
goto :eof
:basename
set %1=%~nx2
goto :eof
It basically calls a function basename (named after the UNIX utility), passing the full name and the variable you want to assign the base name to, and you need to make sure you quote it properly lest filenames containing spaces will cause you problems.
The full set of variable modifiers can be seen in the call /? help output.
Alternatively, you can use the same basename functionality in a one-liner for statement:
for /f "delims=" %%I in ("%var%") do set result=%%~nxI
This allows you to get the base name without having to call a function. I tend to prefer the function myself since it's more readable but you could probably alleviate that by just including a comment:
rem Get base name of var into result:
rem eg: var = C:\Users\user\Desktop\blah blah\yada yada.exe
rem result = yada yada.exe
for /f "delims=" %%I in ("%var%") do set result=%%~nxI

Setting up variable in for loop in batch

I am fighting with little piece of code for last two days.
In this I am not able to set variable in a for loop.
I want to assign a filename to a variable for string manipulation.
echo off
for /f %%a IN ('dir /b *_ah.ttf') DO (
set /a fName=%%~na
echo %fName%
)
When I echo fName variable I get only last filename repeatedly number of times for for loop count.
(I want to pass this variable as an argument to some batch file as follows
ttfhnt --strong-stem-width=D -i %%a %fName:~0,-3%.ttf
but its failing due to above problem)
Can somebody help me please?
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, as it was replaced with the value in the variable.
This same behaviour is seen in lines where several commands are concatenated with &. The line is fully parsed and then executed. If the first commands change the value of a variable, the later commands can not use this changed value because the read operation replace.
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.
And set /A is only used for arithmetic operations
setlocal enabledelayedexpansion
for /f "delims=" %%a IN ('dir /b *_ah.ttf') DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
edited to adapt to comments
While for command is able to execute a command (in the OP code, the dir...), retrieve its output and then iterate over the lines in this output, the original reason for the command is to iterate over a set of files. In this form, the code can be written as
setlocal enabledelayedexpansion
for %%a IN ("*_ah.ttf") DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
Now, the for command replaceable parameter will iterate over the indicated set of files. (execute for /? for a list of all the command options).
But as foxidrive points, the problem with delayed expansion are the exclamation signs. Without delayed expansion, they are another normal character, but with delayed expansion they frequently become a problem when a value containig them is assigned/echoed.
A quick test
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
echo normal : %test%
for /f "delims=" %%a in ("!test!") do echo for : %%a
Will show
---------------------
test=this is a test!
---------------------
delayed : this is a test!
normal : this is a test
for : this is a test
Obviously when the value is a file name, this behaviour will make the code find or not the file.
Depending on the case different solutions can be used, but usually it involves the activation / desactivation of the delayed expansion behaviour (beware, the endlocal removes any change in environment variables from the previous setlocal).
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
rem Commuted to no delayed expansion
setlocal disabledelayedexpansion
echo normal : %test%
endlocal
rem Cancelled the initial enable delayed expansion
for /f "delims=" %%a in ("!test!") do endlocal & echo for : %%a
rem The last endlocal has removed the changes to the variable
echo no data : [%test%]

Resources