Displaying lines from text file in a batch file - batch-file

File test.cmd:
name=dummy
for /f "eol=; tokens=1 delims=," %%i in (list.txt) do (
echo i: %%i
set name=%%i
echo name: %name%)
the file list.txt contains this lines (one name per line):
John
Tom
Erica
Sara
Each time I launch this batch I get this output:
i: John
name: dummy
i:T om
name: dummy
i: Erica
name: dummy
i: Sara
name: dummy
It seems that the variable name does not get assigned the value of %%i
Any idea?

You're falling into the old trap of not using delayed expansion.
For a quick fix, just put
setlocal enabledelayedexpansion
before that loop in your batch file and use !name! instead of %name%.
CMD expands variables while parsing a command. A command in this sense is a single line or a “block”, delimited with parentheses. The complete for loop is only parsed once and in that stage %name% gets replaced with the value it has at that point, namely "dummy". Delayed expansion on the other hand uses ! instead of % to delimit variable names and the variables then get expanded right before execution of a command.
Whenever you are setting a variable inside a parenthesized block and use its value in the same block again you need to use delayed expansion. help set has also some info about this:
Delayed environment variable expansion
is useful for getting around the
limitations of the current expansion
which happens when a line of text is
read, not when it is executed. The
following example demonstrates the
problem with immediate variable
expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" #echo If you see this, it worked
)
would never display the message, since
the %VAR% in both IF statements is
substituted when the first IF
statement is read, since it logically
includes the body of the IF, which is
a compound statement. So the IF
inside the compound statement is
really comparing “before” with “after”
which will never be equal. Similarly,
the following example will not work as
expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will not build up a list of
files in the current directory, but
instead will just set the LIST
variable to the last file found.
Again, this is because the %LIST% is
expanded just once when the FOR
statement is read, and at that time
the LIST variable is empty. So the
actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the
last file found.
Delayed environment variable expansion
allows you to use a different
character (the exclamation mark) to
expand environment variables at
execution time. If delayed variable
expansion is enabled, the above
examples could be written as follows
to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" #echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%

Related

Batch variable from input not being updated

I have the following snippet (some code omitted):
REM Verify destination_folders.
FOR %%f in (%destination_folders%) do (
IF NOT EXIST %%f (
echo(
echo Destination folder %%f does not exist. Is version %version% correct? Please update the script with your required parameters.
SET /p continue="Do you want to abort the operation? [Y/N]:"
echo cont=%continue%
IF %continue:~0,1%==y (
goto :eof
)
IF %continue:~0,1%==Y (
goto :eof
)
)
)
The issue is that the variable continue is not being assigned until after the script has been executed. For example, if continue was N from the the previous execution and then I input Y it will not update it, hence the echo after the assignment will output cont=N although it should be cont=Y. What am I doing wrong?
You need delayed expansion. setlocal enabledelayedexpansion somewhere, and use !continue! instead of %continue%. The problem is that %continue% is expanded as the entire parenthetical code block is read for the first time, so it's treated as flat text by the time for performs its first loop. !continue! retains its variability, and it's re-evaluated on each iteration.
You can also avoid the delayed expansion problem by using choice and if errorlevel like this:
REM Verify destination_folders.
FOR %%f in (%destination_folders%) do (
IF NOT EXIST "%%~f" (
echo(
echo Destination folder %%f does not exist. Is version %version% correct?
echo Please update the script with your required parameters.
choice /N /M "Do you want to abort the operation? [Y/N]: "
if not errorlevel 2 goto :EOF
)
)
To learn in greater detail about delayed expansion, read this portion of the help set documentation:
Finally, support for delayed environment variable expansion has been added. This support is always disabled by default, but may be enabled/disabled via the /V command line switch to CMD.EXE. See CMD /?
Delayed environment variable expansion is useful for getting around the limitations of the current expansion which happens when a line of text is read, not when it is executed. The following example demonstrates the problem with immediate variable expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" #echo If you see this, it worked
)
would never display the message, since the %VAR% in BOTH IF statements is substituted when the first IF statement is read, since it logically includes the body of the IF, which is a compound statement. So the IF inside the compound statement is really comparing "before" with "after" which will never be equal. Similarly, the following example will not work as expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will NOT build up a list of files in the current directory, but instead will just set the LIST variable to the last file found. Again, this is because the %LIST% is expanded just once when the FOR statement is read, and at that time the LIST variable is empty. So the actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the last file found.
Delayed environment variable expansion allows you to use a different character (the exclamation mark) to expand environment variables at execution time. If delayed variable expansion is enabled, the above examples could be written as follows to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" #echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%

Unworking SET in a FOR /L in a batch script

pCan anyone explain why SET is not doing anything in the following code?:
ECHO OFF
FOR /L %%b IN (-1,1,1) DO (
SET /A _lat=%%b
ECHO variable lat: %_lat%
ECHO variable b: %%b
ECHO:
)
output:
variable lat:
variable b: -1
variable lat:
variable b: 0
variable lat:
variable b: 1
I tried SET without /A of course, I tried that within FOR where variable %%b was a string from a file and it worked normally, but just here when variabl %%b is a changing number it does not work.
If you would have any suggestion, please tell me. Of couse I can use just %%b to diplay what I want, but since this is a part of larger scritp, to make it more readable, I would like to but the vaules to approproate variable.
You need to use ENABLEDELAYEDEXPANSION. Try with the below:
setlocal ENABLEDELAYEDEXPANSION
echo off
for /L %%b in (-1,1,1) do (
set /A _lat=%%b
echo variable lat: !_lat!
echo variable b: %%b
echo:
)
Take a look at the output of SET /? in a console. You need to enable and use delayed environment variable expansion for this to work. Here's the relevant snippet from the help set:
Finally, support for delayed environment variable expansion has been
added. This support is always disabled by default, but may be
enabled/disabled via the /V command line switch to CMD.EXE. See CMD /?
Delayed environment variable expansion is useful for getting around
the limitations of the current expansion which happens when a line
of text is read, not when it is executed. The following example
demonstrates the problem with immediate variable expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" #echo If you see this, it worked
)
would never display the message, since the %VAR% in BOTH IF statements
is substituted when the first IF statement is read, since it logically
includes the body of the IF, which is a compound statement. So the
IF inside the compound statement is really comparing "before" with
"after" which will never be equal. Similarly, the following example
will not work as expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will NOT build up a list of files in the current directory,
but instead will just set the LIST variable to the last file found.
Again, this is because the %LIST% is expanded just once when the
FOR statement is read, and at that time the LIST variable is empty.
So the actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the last file found.
Delayed environment variable expansion allows you to use a different
character (the exclamation mark) to expand environment variables at
execution time. If delayed variable expansion is enabled, the above
examples could be written as follows to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" #echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%

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.

How do SETLOCAL and ENABLEDELAYEDEXPANSION work?

I notice in most scripts, the two are usually in the same line as so:
SETLOCAL ENABLEDELAYEDEXPANSION
Are the two in fact separate commands and can be written on separate lines?
Will setting ENABLEDELAYEDEXPANSION have an adverse effect on a script if it is set on the first lines of the script and not disabled until the end of the script?
I think you should understand what delayed expansion is. The existing answers don't explain it (sufficiently) IMHO.
Typing SET /? explains the thing reasonably well:
Delayed environment variable expansion is useful for getting around
the limitations of the current expansion which happens when a line of
text is read, not when it is executed. The following example
demonstrates the problem with immediate variable expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" #echo If you see this, it worked
)
would never display the message, since the %VAR% in BOTH IF statements
is substituted when the first IF statement is read, since it logically
includes the body of the IF, which is a compound statement. So the IF
inside the compound statement is really comparing "before" with
"after" which will never be equal. Similarly, the following example
will not work as expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will NOT build up a list of files in the current directory,
but instead will just set the LIST variable to the last file found.
Again, this is because the %LIST% is expanded just once when the FOR
statement is read, and at that time the LIST variable is empty. So the
actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the last file found.
Delayed environment variable expansion allows you to use a different
character (the exclamation mark) to expand environment variables at
execution time. If delayed variable expansion is enabled, the above
examples could be written as follows to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" #echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
Another example is this batch file:
#echo off
setlocal enabledelayedexpansion
set b=z1
for %%a in (x1 y1) do (
set b=%%a
echo !b:1=2!
)
This prints x2 and y2: every 1 gets replaced by a 2.
Without setlocal enabledelayedexpansion, exclamation marks are just that, so it will echo !b:1=2! twice.
Because normal environment variables are expanded when a (block) statement is read, expanding %b:1=2% uses the value b has before the loop: z2 (but y2 when not set).
ENABLEDELAYEDEXPANSION is a parameter passed to the SETLOCAL command (look at setlocal /?)
Its effect lives for the duration of the script, or an ENDLOCAL:
When the end of a batch script is reached, an implied ENDLOCAL is
executed for any outstanding SETLOCAL commands issued by that batch
script.
In particular, this means that if you use SETLOCAL ENABLEDELAYEDEXPANSION in a script, any environment variable changes are lost at the end of it unless you take special measures.
The ENABLEDELAYEDEXPANSION part is REQUIRED in certain programs that use delayed expansion, that is, that takes the value of variables that were modified inside IF or FOR commands by enclosing their names in exclamation-marks.
If you enable this expansion in a script that does not require it, the script behaves different only if it contains names enclosed in exclamation-marks !LIKE! !THESE!. Usually the name is just erased, but if a variable with the same name exist by chance, then the result is unpredictable and depends on the value of such variable and the place where it appears.
The SETLOCAL part is REQUIRED in just a few specialized (recursive) programs, but is commonly used when you want to be sure to not modify any existent variable with the same name by chance or if you want to automatically delete all the variables used in your program. However, because there is not a separate command to enable the delayed expansion, programs that require this must also include the SETLOCAL part.
A real problem often exists because any variables set inside will not be exported when that batch file finishes. So its not possible to export, which caused us issues. As a result, I just set the registry to ALWAYS used delayed expansion (I don't know why it's not the default, could be speed or legacy compatibility issue.)

Windows XP batch file concat

I'm trying to accomplish the following ridiculous task:
I have a text file containing a set of fully qualified filesnames. I want to iterate through the file and append each line to a common variable, that can be passed to a command line tool. For example, the file might be:
C:\dir\test.txt
C:\WINDOWS\test2.txt
C:\text3.txt
and I'd like to assign them to some variable 'a' such that:
a = "C:\dir\test.txt C:\WINDOWS\test2.txt C:\text2.txt"
A secondary question is - what is a good batch file reference? I'm finding some stuff in the Windows material, and a lot of home-grown websites, but nothing particularly complete.
As for references, SS64.com isn't bad. Rob van der Woude gets linked fairly often, too.
As for your problem, that's easy:
#echo off
setlocal enableextensions enabledelayedexpansion
set LIST=
for /f %%x in (yourfile.txt) do (
set LIST=!LIST! "%%x"
)
echo %LIST%
endlocal
More in-depth explanation:
setlocal enableextensions enabledelayedexpansion
We're enabling delayed expansion here. This is crucial as otherwise we wouldn't be able to manipulate the list of files within the for loop that follows.
for /f %%x in (yourfile.txt) do (
set LIST=!LIST! "%%x"
)
for /f iterates over lines in a file, so exactly what we need here. In each loop iteration we append the next line to the LIST variable. Note the use of !LIST! instead of the usual %LIST%. This signals delayed expansion and ensures that the variable gets re-evaluated every time this command is run.
Usually cmd expands variables to their values as soon as a line is read and parsed. For cmd a single line is either a line or everything that counts as a line, which happens to hold true for blocks delimited by parentheses like the one we used here. So for cmd the complete block is a single statement which gets read and parsed once, regardless of how often the interior of the loop runs.
If we would have used %LIST% here instead of !LIST! then the variable would have been replaced immediately by its value (empty at that point) and the loop would have looked like this:
for /f %%x in (yourfile.txt) do (
set LIST= "%%x"
)
Clearly this isn't what we wanted. Delayed expansion makes sure that a variable is expanded only when its value is really needed. In this case when the interior of the loop runs and constructs a list of file names.
Afterwards the variable %LIST% or !LIST! (now it doesn't really matter anymore which to use) contains the list of lines from the file.
Funnily enough, the help for the set command includes exactly this example for delayed expansion:
Finally, support for delayed
environment variable expansion has
been added. This support is always
disabled by default, but may be
enabled/disabled via the /V command
line switch to CMD.EXE. See CMD /?
Delayed environment variable expansion
is useful for getting around the
limitations of the current expansion
which happens when a line of text is
read, not when it is executed. The
following example demonstrates the
problem with immediate variable
expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" #echo If you see this, it worked
)
would never display the message, since
the %VAR% in BOTH IF statements is
substituted when the first IF
statement is read, since it logically
includes the body of the IF, which is
a compound statement. So the IF
inside the compound statement is
really comparing "before" with "after"
which will never be equal. Similarly,
the following example will not work as
expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will NOT build up a list of
files in the current directory, but
instead will just set the LIST
variable to the last file found.
Again, this is because the %LIST% is
expanded just once when the FOR
statement is read, and at that time
the LIST variable is empty. So the
actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the
last file found.
Delayed environment variable expansion
allows you to use a different
character (the exclamation mark) to
expand environment variables at
execution time. If delayed variable
expansion is enabled, the above
examples could be written as follows
to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" #echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
What you're after can be done with a FOR /F command.
Here's a good resource I've used many times:
http://www.robvanderwoude.com/batchfiles.php
A good book: Windows NT Shell Scripting by Tim Hill. The edition I have was published in 1998 but it is still valid for Windows command programs in Windows 2008.
type *commonfilepart* >> concat_result_file
OS: WINDOWS SERVER 2003

Resources