Page 1 of 1

for loop question

Posted: 20 Jul 2011 14:36
by A_Bobby
Hi,

I have a .txt file the contents of which look like below

Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.17 9.0.30729
McAfee VirusScan Enterprise 8.7.0
WIMGAPI 1.0.0.0
WinPcap 4.1.1 4.1.0.1753
Wireshark 1.2.6 1.2.6
CCM Framework Tools 4.00.5931.0000

Essentially tab delimited software names and versions. Note that the very first line has two version numbers. The first version number is associated with the name itself. I am trying to use the for loop to capture the software name and version in two separate variables. The statement I tried was

for /f "delims= " %i in (test.txt) do @echo %i

It echoes the entire line as %i. I want to have %i declared as name and %j declared and version number.

Re: for look question

Posted: 20 Jul 2011 15:09
by aGerman
I've got an advantage over other forum users: I can see the real code that you pasted.
There are tab characters between name and version, but there are 2 space characters behind delims=
That will never work. On the other hand I cant believe that the entire line is displayed if there are spaces inside.

Regards
aGerman

Re: for loop question

Posted: 20 Jul 2011 15:35
by dbenham
Important: In the code below, <TAB> represents a single tab character. We are unable to post tabs on this site.

Code: Select all

for /f "tokens=1,2 delims=<TAB>" %%i in (test.txt) do @echo NAME=%%i  VERSION=%%j

The code assumes it is within a batch file. Change all %% to % if running on the command line.

I am not able to run the above on the command line because <TAB> invokes name completion on my machine.

Dave Benham

Re: for loop question

Posted: 22 Jul 2011 16:18
by A_Bobby
Thanks Dave. Is there a way to arrest the for loop for each entry and execute additional code before continuing the loop?

e.g for the first entry in the test.txt file, set Name="Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.17" and Version=9.0.30729
I want to run additional code like do a findstr on the two variables Name and version from a separate .xml file for these entries and depending on the errorlevel I get I take more actions and then loop back to the second entry McAfee VirusScan Enterprise 8.7.0 and repeat the process until the last entry in the test.txt file

Thanks,
Bobby

Re: for loop question

Posted: 22 Jul 2011 17:01
by dbenham
Of course :)
Lot's of ways.

1) Create another batch file that processes one Name/Version pair at a time (Probably not the method you want)

parocessNameVer.bat:

Code: Select all

@echo off
echo NAME=%1 version=%2
rem continue with whatever processing you want

main.bat:

Code: Select all

@echo off
for /f "tokens=1,2 delims=<TAB>" %%i in (test.txt) do call processNameVer.bat %%i %%j


2) Do virtually the same thing, but write it as a labeled subroutine that gets called from within a single script:

Code: Select all

@echo off
for /f "tokens=1,2 delims=<TAB>" %%i in (test.txt) do call :processNameVer.bat %%i %%j
exit /b

:processNameVer
echo NAME=%1 version=%2
rem continue with whatever processing you want
exit /b


3) Do all the processing within the FOR loop

Code: Select all

@echo off
for /f "tokens=1,2 delims=<TAB>" %%i in (test.txt) do (
  echo NAME=%%i version=%%j
  rem continue with whatever processing you want
)


Options 1) and 2) are probably the easiest to implement for an inexperienced developer, but are less efficient.

Option 3) is by far the most efficient from a processing time, but it is also more likely to require more advanced coding techniques to circumvent limitations when combining statements in a single block of code.

There are other less generic options as well. If your process is a simple linear chain of tasks with output of one task feeding the input of the next, then a different flavor of FOR could possibly be used (it is very versatile) with pipes within the IN clause and possibly after the DO. It all depends on what you want to do.

Dave Benham

Re: for loop question

Posted: 24 Jul 2011 16:08
by A_Bobby
Thanks again! So here's what I came up with

@echo off
setlocal enabledelayedexpansion
for /f "tokens=1,2 delims=<TAB>" %%i in (test.txt) do (
@echo Name=%%i Ver=%%j
find "%%i" test.xml | if %errorlevel% neq 0 @echo %%i not found
)

But my output is

Name=Microsoft Visual C++ 2005 Redistributable Ver=8.0.56336
0 was unexpected at this time.
Name=Microsoft SQL Server 2008 Setup Support Files Ver=10.1.2731.0
0 was unexpected at this time.
Name=WinPcap 4.1.2 Ver=4.1.0.2001
0 was unexpected at this time.
Name=Wireshark 1.4.0 Ver=1.4.0
0 was unexpected at this time.

With echo on I noticed it executes ok up to errorlevel but @echo does not get executed.

Update: I figured it out. Its the neq. Have to use == instead. But my output is not what I want. I am always ending up with %errorlevel% 0 regardless of if the find matches or not. Please help.

Re: for loop question

Posted: 27 Jul 2011 11:07
by dbenham
When posting, please enclose your code in Code tags - there is a button at the top of the message editor for that purpose.

You are running into some of the problems I warned you about with using option 3. It would be a good exercise for you to practice using a function call. Performance is not likely to be an issue.

That being said - here are some problems with your code below:

Code: Select all

@echo off
setlocal enabledelayedexpansion
for /f "tokens=1,2 delims=<TAB>" %%i in (test.txt) do (
@echo Name=%%i Ver=%%j
find "%%i" test.xml | if %errorlevel% neq 0 @echo %%i not found
)
  • The value of %errorlevel% is determined when the statement is parsed - It remains constant throughout each loop iteration, as you have discovered. You need to use delayed expansion !errorlevel! instead. This is the primary complication of complex blocks of code.
  • FIND will output each line of text that matches your search string, but you are not interested in those lines of text. You should not be piping the output to IF (which cannot work). Instead you should be "disabling" output by redirecting the output to NUL.
  • The IF statement should be on the next line, or you can use the && (do this if success) || (do this if error) syntax
  • This is not really a bug, but the @ is not needed after the 1st line where you set ECHO OFF


Here is one corrected (but untested) version of the code (remember that <TAB> represents a tab character):

Code: Select all

@echo off
setlocal enabledelayedexpansion
for /f "tokens=1,2 delims=<TAB>" %%i in (test.txt) do (
  echo Name=%%i Ver=%%j
  find "%%i" test.xml >nul
  if !errorlevel! neq 0 echo %%i not found
)


Here is the same thing using || syntax:

Code: Select all

@echo off
setlocal enabledelayedexpansion
for /f "tokens=1,2 delims=<TAB>" %%i in (test.txt) do (
  echo Name=%%i Ver=%%j
  find "%%i" test.xml >nul || (echo %%i not found)
)

Warning: The following look similar, but they are completely different :!:
CMD1 | CMD2 = Pipe output of CMD1 as input to CMD2
CMD1 || CMD2 = Execute CMD2 only if CMD1 failed


Dave Benham

Re: for loop question

Posted: 27 Jul 2011 11:18
by A_Bobby
I pretty much gave up on option 3 and using option 2 I made the damn thing work... finally... but thanks for the tips. Here is the code if someone wants to use it for something... It will save my team a lot of man hours.

Essentially I am parsing text file that has software name/versions and trying to find those in a raw xml file that the system spits out. If it finds the name, it jumps to check the version number. A complete match is ignored and no match will spit it out to a log file. Who say's you can't do much with batch scripting :)

Thanks a lot to this site and the experts for providing insights.

@echo off
if exist log.txt del log.txt
for /r %%a in (xmls\*) do echo. >>log.txt &set File=%%a &call :Start
exit /b
:Start
for /f "tokens=1,2 delims=<tab>" %%i in (test.txt) do set Name="%%i" &set Ver="%%j" &call :NameVer
exit /b
:NameVer
@echo off
@echo Name=%Name% Version=%Ver%
find %Name% %File%
if not %errorlevel% == 0 @echo Name %Name% not found in file %File% >>log.txt & goto :end
find %Ver% %File%
if not %errorlevel% == 0 @echo Version number %Ver% for %Name% does not match in file %File% >>log.txt
:end
exit /b