I have a simple text file containing one file name per file. I want to merge all of these files. My plan for this was to read the text file, build a string like "filename1+file2+f3" and then use that as a parameter to copy /b.
However, I am having trouble reading the file correctly.
Here is what I have right now:
SET x=
FOR /F %%G IN (merge.txt) DO SET x=%x%+%%G
ECHO %x%
However, the "recursion" here does not seem to work properly and %x% just gets set to "+fl", where fl is the last filename in the file.
How do I do this properly?
Your logic is correct, but you are just missing the delayed expansion usage.
SETLOCAL EnableDelayedExpansion
SET "x="
FOR /F %%G IN (merge.txt) DO SET x=!x!+%%G
ECHO %x%
REM Trim the leading +
SET x=%x:~1,999%
ECHO %x%
ENDLOCAL
Without the delayed expansion, %x% is only evaluated when the FOR loop starts, so it would be blank for each iteration. By enabling delayed expansion, !x! (the notation for this) is evaluated on each iteration so it will build the compound string you are looking for.
Related
Can someone give me an example of where a batch script would act differently with or without delayed expansion? Are there any situations where you would NOT want to use delayed expansion? Thanks.
Look at the following examples...
Example 1: The following code DOESN'T use delayed expansion, so the variables in the for loop are expanded only one time. This means that %Count% will always expand to 0 in each iteration of the loop, no matter what we do to it with the set command:
#echo off
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=%COUNT% + 1
echo Count = %COUNT%
)
pause
So this script will output:
Count = 0
Count = 0
Count = 0
Count = 0
This is not how this loop is supposed to work.
Example 2: On the other hand, if we use delayed expansion, we have the following script, which will run as expected.
setlocal ENABLEDELAYEDEXPANSION
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=!COUNT! + 1
echo Count = !COUNT!
)
pause
and, as expected, it will output:
Count = 1
Count = 2
Count = 3
Count = 4
When you use the ENABLEDELAYEDEXPANSION, and expand a variable using ! instead of %, the variable is re-expanded each time, and everything works as it's supposed to.
I wanted to add a great example on how "EnableDelayedExpansion" (EDE) can be useful outside of the ubiquitous FOR loop examples.
Here is a line of earthquake data that I wish to parse (I call it it 1line.txt)
ak_11574812 2015.04.29.193822 62.9525 -148.8849 1.0 9.5 1 49km S of Cantwell, Alaska
The problem I ran into was that last segment of this line does not always start at the same column number. So I needed to create a flexible SET command that will accurately pluck out the last segment of this line.
ECHO OFF
setlocal enableDelayedExpansion
set where=72
set /p line=<1line.txt
set locate=!line:~%where%,28!
echo %locate%
EDE allows me to place a variable (where) inside another variable (line). EDE will translate the variable bracketed by % first, then process the variable bracketed by ! and (in this case) push out the results into the "locate" variable.
Max's answer gives an example of where a batch script would act differently with or without delayed expansion.
For the sake of completeness, let's answer another part of the question and show a situation where you would NOT want to use delayed expansion when your data contain an exclamation mark ! (and show two ways of processing such data):
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "_auxFile=%temp%\%~n0.txt"
rem create multiline sample file
>"%_auxFile%" ( for /L %%G in (1,1,3) do echo line %%G is 100%% valid! Sure! Hurrah!)
rem create one-line sample file
>"%_auxFile%" echo this line is 100%% valid! Sure! Hurrah!
echo(
echo --- file content
type "%_auxFile%"
echo(
SETLOCAL EnableDelayedExpansion
echo --- enabled delayed expansion chokes down unescaped exclamation marks^^^! "^!"
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%~G"
echo loop var=%%~G
echo _auxLine=!_auxLine!
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- toggled delayed expansion works although might be laborious!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
SETLOCAL EnableDelayedExpansion
echo _auxLine=!_auxLine!
ENDLOCAL
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- keep delayed expansion DISABLED: use CALL command!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
call :ProcessVar
)
ENDLOCAL
rem delete the sample file
del "%_auxFile%"
ENDLOCAL
goto :eof
:ProcessVar
echo _auxLine=%_auxLine%
echo WARNING: neither !_auxLine! nor %%G loop variable is available here!
goto :eof
Note that above script shows proper ways of escaping
% percent sign by %% doubling it (delayed expansion does not matter), and
! exclamation mark if delayed expansion is enabled:
"^!" if enclosed in a pair of double quotes, then use the cmd and batch-script general escape character ^ caret;
^^^! otherwise, use three ^ carets.
Output:
==> D:\bat\SO\10558316.bat
--- file content
this line is 100% valid! Sure! Hurrah!
--- enabled delayed expansion chokes down unescaped exclamation marks! "!"
loop var=this line is 100% valid Hurrah
_auxLine=this line is 100% valid Hurrah
--- toggled delayed expansion works although might be laborious!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
--- keep delayed expansion DISABLED: use CALL command!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
WARNING: !_auxLine! as well as %G loop variables are not available here!
==>
As pointed in the answer the main usage of the delayed expansion is the setting and accessing variables in brackets context.
Though it can be useful in another situations too.
Parametrizing substring and string substitution:
#echo off
setlocal enableDelayedExpansion
set "string=test string value"
set start=5
set get_next=6
echo #!string:~%start%,%get_next%!#
set "search_for=string"
set "replace_with=text"
echo #!string:%search_for%=%replace_with%!#
the output will be:
#string#
#test text value#
though this can be achieved with additional call this way is more performant
Using shift command within brackets parameterized argument access
#echo off
echo first attempt:
(
echo "%~1"
shift
echo "%~1"
)
::now the shift command will take effect
setlocal enableDelayedExpansion
echo second attempt:
(
set /a argument=1
call echo %%!argument!
shift
call echo %%!argument!
)
the output will be:
first attempt:
"first argument"
"first argument"
second attempt:
"second argument"
"third argument"
As you can see parameterized argument access can be done only with delayed expansion.
Using for tokens (or function arguments) for parameterization
One more approach for mixing !s and %s this could be useful for nested loops:
#echo off
setlocal enabledelayedexpansion
set begin=2
set end=2
set string=12345
for /f "tokens=1,2" %%A in ("!begin! !end!") do set "string2=!string:~%%A,%%B!"
echo !string2!
endlocal
as you can see now the for command tokens are used as parameters.
Several answers here answer the "How to use delayed expansion?" question or what happen if you don't use delayed expansion. However, the second question is "Are there any situations where you would NOT want to use delayed expansion?" and a couple answers take this question as "how to avoid the problems caused by using delayed expansion?"
My answer answers the question as I understand it: "In which situations is better to NOT use delayed expansion (instead of use it)?"
If you want to exchange the contents of two variables, the simplest way to perform this is using the %standard% variable expansion:
set "var1=%var2%" & set "var2=%var1%"
The way that the %standard% expansion works makes possible to achieve this replacemenmt without using any auxiliary variable. As far as I know, the Batch-file "programming language" is the only one that allows to perform this exchange in this way, that is, making good use of a language "feature" (not via a specialized "exchange" instruction/statement).
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.
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%]
I am writing a CMD/batch file (running under Win-7 cmd.exe) and I seem to be getting hung up on delayed variable expansion.
I am using text file input that is in the form:
9 .CN=ISRJX.OU=Linc.OU=thisco.O=UCOM.T=UCOM. 8-20-13
10 .CN=FXXLISHER.OU=Linc.OU=thisco.O=UCOM.T=UCOM. 10-13-13
11 .CN=QXX004F.OU=Linc.OU=thisco.O=UCOM.T=UCOM. 10-14-13
12 *.CN=QXX1001OB.OU=Linc.OU=thisco.O=UCOM.T=UCOM. 10-15-13
as contents in "inputfile.txt". Purpose is to extract the first word after ".CN=", at this point in the process.
Note that I can't strip based on number of chars before "CN=" because the number of chars will vary.
My script is:
setlocal enableextensions enableDelayedExpansion
REM #echo off
for /f "tokens=3 delims==." %%a in (inputfile.txt) do (
set "acct =%%a"
echo. %%a,%acct%
)
endlocal
I've tried every set of combination of quote, !, % etc. and both enabled & disabled delayed expansion, and still can't get this to work. For the most part, when I use ! I end up echoing the actual !. i.e. "echo !acct!" will echo the actual text "!acct!".
I have read many examples and answers, here and elsewhere, about delayed variable expansion. I just can't figure out how to work around it in this example, where I want "acct" to expand within the loop.
Suspect this is a simple punctuation problem, but I'm out of ideas. When I run this, I see acct set to the value for %%a, but when it echoes, clearly it doesn't expand to the new value -- instead it will echo whatever it was set to previously, or blank.
I have also tried disabledelayedexpansion, which makes no difference in my results (including when I use !acct! instead of %acct%.)
Remove the space after the acct variable name and use the ! marks.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
for /f "tokens=3 delims==." %%A in (inputfile.txt) do (
set "acct=%%A"
echo. %%A,!acct!
)
endlocal
There are more than 10 html files with image tags. Every time we deploy our build onto test site we need to change the img source. for eg <img src=/live/Content/xyz.png />
to <img src=/test/Content/xyz.png />.
After looking around and reading for sometime, i have come up with the following batch script, however i cant figure out how do i go further from here :
for /r %%i in (*.html) do echo %%i
for %%f in (*.html) do (
FOR /F %%L IN (%%f) DO (
SET "line=%%L"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "x= <--------------------WHAT DO I SET HERE?
echo %x%
ENDLOCAL )) pause
This is my first batch script, could anyone please guide me in the right direction?
#ECHO OFF
SETLOCAL enabledelayedexpansion
for /r U:\ %%i in (*.html) do (
echo found %%i
SET outfile="%%~dpni.lmth"
(
SETLOCAL disabledelayedexpansion
FOR /F "usebackq delims=" %%L IN ("%%i") DO (
SET "line=%%L"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "line=!line:/live/=/test/!
echo !line!
ENDLOCAL
)
ENDLOCAL
)>!outfile!
)
pause
GOTO :EOF
How about this development?
Notes:
I've modified your FOR/R to ECHO the HTML file being processed and use %%i rather than switching to %%f. U: is my RAMDRIVE; you'd need to modify that to suit.
outfile is set to generate a filename which matches the HTML filename, but with a .lmth extension (can't update in-place) - it gets that from the ~dpn prefixing the i, which means the drive, path and name of the file %%i. It's quoted to take care of potential spaces in the filename or pathname.
The next logical statement is (for /f...[lines] )>!outfile! which sends any echoed text to a NEW file !outfile!. The enabledelayedexpansion in the second physical line of the batch makes !outfile! the RUN-TIME value - as it is changed within the FOR r outer loop.
Since the actual HTML filename in %%i may contain spaces, it needs to be quoted, hence the 'usebackq' clause in the FOR/F. The delims= clause ensures that the ENTIRE line from the file "%%i" is applied to %%L - not just the first token (well, actually, makes the entire line the first token).
The SET command substitutes the string "/test/" for any occurrence of "/live/" in the RUN-TIME value of the variable lineand assigns the result to line. The resultant value is then ECHOd - which is redirected to outfile
Note that in your original, you would be assigning x in the set x= but echo %x% would have reproduced x as it stood when the line was PARSED because batch substitutes the value of any variable for %var% as part of the parsing phase. Consequently, the line would have become simply ECHO (since x would likely be unassigned) and bizarrely would have reported the echo state (Echo is OFF)
A couple of gatchas here. First, % and some other characters are notoriously hard to process with batch, so be careful. Next, FOR/F will bypass empty lines. This can be overcome if required. Third, this will replace ANY occurrence of /live/ in any case with /test/
Good luck!
Edit to support exclamation marks: 20130711T0624Z
Added SETLOCAL enabledelayedexpansion line and ENDLOCAL just before )>!outfile! to match