Using Findstr with specific range or other method - windows

I only want to find specific lines in his text file, so i figure that using range would be a good idea. But i can seem to find any tutorial online. please help..
An example of the text file
XXX scan report for 192.0.0.0
exampleexampleexampleexampleexample
OS: windows 8
exampleexampleexampleexample
exampleexampleexampleexample
PORT STATE SERVICE VERSION
21/tcp close ftp
80/tcp open http Microsoft ISS
exampleexampleexampleexample
exampleexampleexampleexample
XXX scan report for 192.0.0.1
exampleexampleexampleexampleexample
exampleexampleexampleexample
exampleexampleexampleexample
PORT STATE SERVICE VERSION
21/tcp close ftp
80/tcp open http Microsoft ISS
exampleexampleexampleexample
exampleexampleexampleexample
I wanted to get IP address, OS Details and Port status into a spreadsheet.
My code:
#echo off
set file=C:\Users\1.txt
set match=report for
set match1=OS
findstr /c:"%match%" %file%
findstr /c:"%match1%" %file%
findstr /c:"tcp " /c:"udp " %file%
for /f "tokens=1-5" %%a in ('findstr /c:"%match%" %file%') do (
echo "IP Address:","%%e" >> report1.csv
for /f "tokens=1,2 delims=:*" %%a in ('findstr /c:"%match1%" %file%') do
(
echo "Operating System: ","%%b" >> report1.csv
echo "PORT","STATE","SERVICE","VERSION" >> report1.csv
for /f "tokens=1-4 delims=: " %%a in ('findstr /c:"tcp " /c:"udp "
%file%') do (
echo "%%a","%%b","%%c","%%d" >> report1.csv
)
)
)
There is a big problem with this code and it is in the for loop.
The code will get the first ip then will proceed to get the os details, but not ever ip have the os details, so the os details will be placed in the wrong ip.
Another problem with the code is that it will list all os and port details under one ip address. And the next ip address will also be the same, it will have all the os and port details as well.
Please do help me to solve this problem. Or is there any other method like call?

#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q43335765.txt"
SET "outfile=%destdir%\outfile.txt"
>"%outfile%" ECHO IP,OS,PORT,STATUS,SERVICE,VERSION
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO (
CALL :process %%a
)
GOTO :EOF
:process
ECHO %*|FIND "scan report for " >NUL
IF NOT ERRORLEVEL 1 GOTO newip
IF "%~1"=="OS:" SET "$os=%*"&GOTO :eof
ECHO %~1|FIND "/">NUL
IF ERRORLEVEL 1 GOTO :EOF
FOR /f "tokens=1,2,3*" %%p IN (
"%*") DO >>"%outfile%" ECHO %$ip%,%$os:*: =%,%%p,%%q,%%r,%%s
GOTO :eof
:newip
IF "%~2" neq "" shift&GOTO newip
SET "$ip=%~1"
SET "$os=: "
GOTO :eof
You would need to change the settings of sourcedir and destdir to suit your circumstances.
I used a file named q43335765.txt containing your data for my testing.
Produces the file defined as %outfile%
Having established the filenames, put the header line in the output file and process each line of the file through :process
In :process, detect the key string indicating a ne data item. If found, go to :newip which simply shuffles the data line along until only the last entry remains and assign this last entry to $ip. Set $ip to :Space + any special string you want to represent "not found" (like unknown for instance)
If the line doesn't contain the keystring, see whether the first token is OS:, and set $os to the entire original line if it is.
Otherwise, lookk for a / in the first token. If it's not there, abandon processing this line, otherwise simply tokenise the line, selecting the first to thid and rest and output using the saved ip, the saved os, except for the part before the first :Space and the four data line entries, all separated by commas.
Revision to cater for | in data - replace with /
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q43335765.txt"
SET "outfile=%destdir%\outfile.txt"
>"%outfile%" ECHO IP,OS,PORT,STATUS,SERVICE,VERSION
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO (
SET "line=%%a"
CALL :preprocess
)
GOTO :EOF
:preprocess
SET "line=%Line:|=/%"
CALL :process %line%
GOTO :eof
:process
ECHO %*|FIND "scan report for " >NUL
IF NOT ERRORLEVEL 1 GOTO newip
IF "%~1"=="OS:" SET "$os=%*"&GOTO :eof
ECHO %~1|FIND "/">NUL
IF ERRORLEVEL 1 GOTO :EOF
FOR /f "tokens=1,2,3*" %%p IN (
"%*") DO >>"%outfile%" ECHO %$ip%,%$os:*: =%,%%p,%%q,%%r,%%s
GOTO :eof
:newip
IF "%~2" neq "" shift&GOTO newip
SET "$ip=%~1"
SET "$os=: "
GOTO :eof
Shows the need to provide a representative data sample...
With characters that have special meaning like |, assign to a variable and call a preprocessor to convert all | to / (or whatever else is desired) and then call the process with the result.
Formula: set "var=%somevar:string1=string2%"
will assign to var the value of somevar with all occurrences of string1 replaced by string2. The enclosing quotes in a set command ensure that any stray trailing spaces on the line are not included in the value assigned.

The flaw in your logic is that each time you execute findstr, it starts back at line 1 of your text file. If you're working with multi-line records, you have to build your line of output with in one pass of the record. I suggest a for /F loop and testing tokens is better suited to this than findstr. Here's a suggestion:
#echo off
setlocal
set "file="C:\Users\1.txt"
>"report1.csv" (
echo "IP Address","Operating System","port 21","port 80"
for /F "usebackq tokens=1-3,5" %%I in ("%file%") do (
rem // check if line begins a record
if /i "%%~J"=="scan" if /i "%%~K"=="report" (
set "IP=%%~L"
set "OS="
)
rem // check if line contains OS
if /i "%%~I"=="OS:" set OS="%%~J %%~K"
rem // check if line contains port 21
if /i "%%~I"=="21/tcp" set "state21=%%~J"
rem // port 80 indicates end of needed info in record
setlocal enabledelayedexpansion
if /i "%%~I"=="80/tcp" echo(!IP!,!OS!,!state21!,%%~J
endlocal
)
)

Related

Creating Each line of text as variable and them constantly changing in a loop in batch

So what I'm trying to do is create a find for multiple people where it in the text file it will say names and numbers like
Example of text file:
Beth
1234567891
Jay
2134456544
This is the best way I can explain what I'm trying to do:
#echo off
set "file=Test1.txt"
setlocal EnableDelayedExpansion
<"!file!" (
for /f %%i in ('type "!file!" ^| find /c /v ""') do set /a n=%%i && for /l %%j in (1 1 %%i) do (
set /p "line_%%j="
)
)
set /a Name=1
set /a Number=2
Echo Line_%Name%> %Name%.txt (Im trying to get this to say line_2 to say 1st line in the text file)
Echo Line_%Number%> %Name%.txt (Im trying to get this to say line_2 to say 2nd line in the text file)
:Start
set /a Name=%Name%+2 (These are meant to take off after 1 so lines 3,5,7,9 so on)
set /a Number=%Number%+2 (These are meant to take off after 2 so lines 4,6,8,10 so on)
Echo Line_%Name%
Echo Line_%Number%
GOTO :Start
so the outcome would be
In Beth.txt:
Beth
1234567891
So every name will be a file name and the first line in a file. I will change it later so I can do a addition in each text file.
Name: Beth
Number: 1234567891
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=u:\your files"
SET "destdir=u:\your results"
SET "filename1=%sourcedir%\q65417881.txt"
rem make sure arrays are empty
For %%b IN (name number) DO FOR /F "delims==" %%a In ('set %%b[ 2^>Nul') DO SET "%%a="
rem Initialise counter and entry array
SET /a count=0
SET "number[0]=dummy"
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO (
IF DEFINED number[!count!] (SET /a count+=1&SET "name[!count!]=%%a") ELSE (SET "number[!count!]=%%a")
)
rem clear out dummy entry
SET "number[0]=dummy"
FOR /L %%c IN (1,1,%count%) DO (
rem replace spaces with dashes
SET "name[%%c]=!name[%%c]: =-!"
rem report to console rem report to console
ECHO Name: !name[%%c]! Number: !number[%%c]!
rem generate name.txt file
(
ECHO !name[%%c]!
ECHO !number[%%c]!
)>"%destdir%\!name[%%c]!.txt"
)
GOTO :EOF
You would need to change the values assigned to sourcedir and destdir to suit your circumstances. The listing uses a setting that suits my system.
I deliberately include spaces in names to ensure that the spaces are processed correctly.
I used a file named q65417881.txt containing your data for my testing.
The line data read from the file is assigned to %%a is assigned to and number[!count!] alternately. The data is retained in these arrays for use by further processing.
[Edited to include conversion of spaces within names to dashes]
If I understand correctly, you want to precede every second line with Number: + SPACE and every other line with Name: + SPACE. For this you do not need to store each line in a variable first, you can use a single for /F loop lo read the file line by line and process every line individually. There are two possibilities:
Temporarily precede every line with a line number plus : using findstr /N:
#echo off
rem // Loop through lines and precede each with line number plus `:`:
for /F "tokens=1* delims=:" %%K in ('findstr /N "^" "Test1.txt"') do (
rem // Calculate remainder of division by two:
set /A "MOD=%%K%%2" 2> nul
rem // Toggle delayed expansion to avoid issues with `!`:
setlocal EnableDelayedExpansion
rem // Conditionally return line string with adequate prefix:
if !MOD! neq 0 (
endlocal & echo Name: %%L
) else (
endlocal & echo Number: %%L
)
)
This will fail when a line begins with the a :.
Check whether numeric representation of current line string is greater than 0:
#echo off
rem // Loop through (non-empty) lines:
for /F "usebackq delims=" %%L in ("Test1.txt") do (
rem // Determine numeric representation of current line string:
set /A "NUM=%%L" 2> nul
rem // Toggle delayed expansion to avoid issues with `!`:
setlocal EnableDelayedExpansion
rem // Conditionally return line string with adequate prefix:
if !NUM! equ 0 (
endlocal & echo Name: %%L
) else (
endlocal & echo Number: %%L
)
)
This fails when a name begins with numerals and/or when a numeric line is 0.
And just for the sake of posting something different:
#SetLocal EnableExtensions DisableDelayedExpansion & (Set LF=^
% 0x0A %
) & For /F %%G In ('Copy /Z "%~f0" NUL') Do #Set "CR=%%G"
#For /F "Tokens=1,2* Delims=:" %%G In ('%__AppDir__%cmd.exe /D/V/C ^
"%__AppDir__%findstr.exe /NR "^[a-Z]*!CR!!LF![0123456789]" "Test1?.txt" 2>NUL"
') Do #(SetLocal EnableDelayedExpansion
(Set /P "=Name: %%I!CR!!LF!Number: " 0<NUL & Set "_="
For /F Delims^=^ EOL^= %%J In ('%__AppDir__%more.com +%%H "%%G"') Do #(
If Not Defined _ Set "_=_" & Echo %%J)) 1>"%%I.txt" & EndLocal)
This file should be run with the Test1.txt file in the current working directory. It is important that along side Test1.txt, there are no other .txt files with the same basename followed by one other character, (for example Test1a.txt or Test12.txt). Should you wish to change your filename, just remember that you must suffix its basename in the above code with a ? character, (e.g. MyTextFile.log ⇒ MyTextFile?.log).
I had the rare opportunity to verify that this script worked against the following example Test1.txt file:
Beth
1234567891
Jay
2134456544
Bob
2137856514
Jimmy
4574459540
Mary
3734756547
Gemma
6938456114
Albert
0134056504

Batch file - findstr command fails using multiple strings to search for

This batch file loops through a list of computer IP addresses and passwords with a comma separator (IP_List.txt). Commands are then run to connect remotely to each computer. I've got another list of IP's which are exceptions and need to be skipped, so I've got a variable (set str=) which lists these IP's and I'm using findstr to search for them:
#ECHO OFF
setlocal EnableDelayedExpansion
Pushd "%~dp0"
Set Log=LogFile.log
set str=172.16.66.1 172.16.66.2 172.16.66.3 172.16.66.4
FOR /F "tokens=1,2 delims=," %%i in (IP_List.txt) do call :process %%i %%j
(ECHO+ & ECHO --- END OF REPORT ---) >> %Log%
GOTO :EOF
:process
set IP=%1
set PW=%2
#ECHO %IP% | findstr "%str%" >nul
IF NOT ERRORLEVEL 1 (ECHO+ & ECHO %IP% & ECHO This IP address was skipped) >> %Log% & GOTO :EOF
#ECHO Processing %IP% >> %Log%
---- (remainder of batch file here) ----
There was a problem with findstr in that having 172.16.66.2 on the exception list, findstr was mistakenly finding a match with the following IP's on my overall list: 172.16.66.224, 172.16.66.225, 172.16.66.226, etc.
After changing the code and putting the list of IP exceptions in a text file (IP_Exceptions.txt) I was able to get past the findstr problem and this modified batch file works without issue:
#ECHO OFF
setlocal EnableDelayedExpansion
Pushd "%~dp0"
Set Log=LogFile.log
FOR /F "tokens=1,2 delims=," %%i in (IP_List.txt) do call :process %%i %%j
(ECHO+ & ECHO --- END OF REPORT ---) >> %Log%
GOTO :EOF
:process
set IP=%1
set PW=%2
::~~ Add a right bracket to the end of the IP address variable
SET IPstr=%IP%]%x%
set match=N
::~~ Loop through a list of IP address exceptions
::~~ (A left bracket has been added to the beginning of
::~~ each IP on the list in order to use it as a delimiter)
::~~ Add a right bracket to the end of the variable "%%j"
::~~ in order to look for a match with the findstr command
for /F "delims=[" %%i in (IP_Exceptions.txt) do (
for /F "tokens=1" %%j in ("%%i") do (
ECHO %%j] | findstr "%IPstr%" >nul
IF not errorlevel 1 set match=Y
)
)
IF %match% == Y (ECHO+ & ECHO %IP% & ECHO Skipped this machine) >> %Log% & GOTO :EOF
#ECHO Processing %IP% >> %Log%
:: ---- (remainder of batch file here) ----
The specifics of getting the findstr section to work correctly without false matches (adding brackets to variables, nested for loops with delimiters, etc.) was kind of clumsy though. Any suggestions on making that section more efficient would be greatly appreciated.
A modified batch file works but it's clumsy:
#ECHO OFF
setlocal EnableDelayedExpansion
Pushd "%~dp0"
Set Log=LogFile.log
FOR /F "tokens=1,2 delims=," %%i in (IP_List.txt) do call :process %%i %%j
(ECHO+ & ECHO --- END OF REPORT ---) >> %Log%
GOTO :EOF
:process
set IP=%1
set PW=%2
::~~ Add a right bracket to the end of the IP address variable
SET IPstr=%IP%]%x%
set match=N
::~~ Loop through a list of IP address exceptions
::~~ (A left bracket has been added to the beginning of
::~~ each IP on the list in order to use it as a delimiter)
::~~ Add a right bracket to the end of the variable "%%j"
::~~ in order to look for a match with the findstr command
for /F "delims=[" %%i in (IP_Exceptions.txt) do (
for /F "tokens=1" %%j in ("%%i") do (
ECHO %%j] | findstr "%IPstr%" >nul
IF not errorlevel 1 set match=Y
)
)
IF %match% == Y (ECHO+ & ECHO %IP% & ECHO Skipped this machine) >> %Log% & GOTO :EOF
#ECHO Processing %IP% >> %Log%
:: ---- (remainder of batch file here) ----
Your subroutine could look like this:
...
:process
echo/ %str% |findstr /LC:" %1 " >nul && (
echo %1 skipped
goto :eof
)
echo processing %1 with %2
:: ---- (remainder of batch file here) ----
Note that I reversed the "search" logic (to make it easier to process spaces correctly)
Instead of echo <sub>|findstr <whole>, I switched to echo <whole> |findstr <sub>
Note: the spaces in echo/ %str% |findstr /LC:" %1 " are critical (they are also compared to avoid false positives).
/LC: tells findstr to search literal (treating the dot as a dot instead of a wildcard) and including spaces.

Batch code to turn every line of text to a variable

So I need a code that will take a text file (we'll cal it list.txt) and turn every line into a variable.
To give some context, I have some file names listed in list.txt, which adds and deletes file names occasionally by user request. I want the user of this code to be able to select which document they'd like to open using variables.
For example, if list.txt looks like this
list.txt
loading.txt
test1.txt
test2.txt
test3.txt
test4.txt
Then I'd like an automatic variable for every .txt listed. I then would add a simple if statement to open the file matched with the variable.
Am I making this too complicated for myself or is this the only way to do this?
EDIT:
I am not attempting something like this:
type list.txt
echo.
echo.
set /p GROUPSELECT= Please type out the FULL name of the group listed:
CD %grouplist%
type %GROUPSELECT%
It will display the file contents, and then display the specific file chosen by the input. I'd think that the variable might be easier to do more with later though, just a thought.
Edit2
I tried this:
#Echo OFF
FOR /F "Usebackq Delims=" %%a IN (
"list.txt"
) DO (
set jim=%%a
)
echo %jim%
PAUSE
%jim% will only be the last line in the text file, so how do I make the next step into making them all different?
Give this a try. I have commented each line of code that should explain what it is doing. Let me know if you need any further explanation.
#echo off
REM FOR commnd is parsing the output of the FINDSTR commands.
REM First FINDSTR command is finding non empty lines in the file
REM Second Findstr command is assigning a number to each line of the file
REM The output is split into two tokens and the number is assigned to %%G and the line of the file to %%H
for /F "tokens=1* delims=:" %%G IN ('findstr /V "^$" list.txt ^|findstr /N ".*"') DO (
REM create a variable of the lines in the file
set "file%%G=%%H"
REM Create a menu of the lines in the file
echo %%G %%H
REM Get the number of lines in the output
set "num=%%G"
)
REM Ask user to chose a number
set /P "filenum=Chose a file number 1 to %num%:"
REM Need to enable delayed expansion to use array variables
setlocal enabledelayedexpansion
REM Check if they type in a correct number and display the file
IF DEFINED file%filenum% type !file%filenum%!
endlocal
pause
Is this the sort of thing you're looking for?
#For /F "Delims==" %%# In ('Set # 2^>Nul')Do #Set "%%#="
#For /F Tokens^=1*Delims^=[]^ EOL^= %%# In ('Type "file.txt"^|Find /V /N ""'
)Do #Set "#%%#=%%$"&Echo( %%#. %%$
:Opt
#Echo(
#Set /P "Opt=Choose a document to open>"
#Set #|Findstr /B "#%Opt%=">Nul||GoTo :Opt
#Call Start "" "%%#%Opt%%%"
Just change the name of the text file containing your list on line 2 as needed.
#TheBoy your question is confusingly framed. If you DO NOT want the example you put in the edit, then can you better explain what you DO want??
Do you want to say iterate over the list file, and create a choice screen?
#(SETLOCAL EnableDelayedExpansion
ECHO OFF
SET "_ChoiceList_File=C:\Admin\ChoiceFile.txt"
REM Full Character List to populate Choices
SET "_CharList=0 1 2 3 4 5 6 7 8 9 A B C D E F N X"
)
CALL :Main
( ENDLOCAL
EXIT /B 0
)
:Main
REM Now we can Do Our Choices btu lets do it in a Sub Function.
CALL :MakeChoice _ChoiceResult
ECHO.
ECHO The Index Chosen Was: %_Chosen%
ECHO The Result Matched is: "%_ChoiceResult%"
REM ECHO Here you output "%_ChoiceResult%"
TYPE "%_ChoiceResult%"
GOTO :EOF
:MakeChoice
cls
color 1A
SET %~1="
SET "_Choices="
SET "_Chosen="
SET "_Amount="
SET "_Choice.17.Value=Next Set!"
SET "_Choice.18.Value=EXIT!"
SET "_Choice_Show_Next="
echo. Pick a File:
echo.========================
REM Create Numbered Array of Choices and output Choices to the Screen
FOR /F "tokens=* usebackq" %%A IN ("%_ChoiceList_File%") DO (
SET /A "_Amount+=1"
SET "_Choice.!_Amount!.Value=%%A"
IF !_Amount! EQU 16 (
SET /A "_Amount+=2"
CALL :MakeChoice "%~1"
IF DEFINED _Chosen (
IF !_Chosen! NEQ 17 (
REM IF !_Chosen! NEQ 18 (
GOTO :EOF
REM )
)
SET "_Amount="
SET "_Chosen="
)
)
)
IF NOT DEFINED _Chosen (
SET /A "_Amount+=1"
SET "_Choice.!_Amount!.Value=!"
SET /A "_Amount+=1"
SET "_Choice.!_Amount!.Value=EXIT!"
CALL :MakeChoice "%~1"
)
GOTO :EOF
:MakeChoice
CLS
ECHO.
SET "_Temp="
SET "_Choices="
SET /A "_Skipline= !_Amount! - 1"
REM Create Choice List to Display only the choices needed.
FOR %%A IN (%_CharList%) DO (
SET /A "_Temp+=1"
IF !_Temp! LEQ !_Amount! (
IF !_Temp! EQU !_Skipline! (
ECHO.
ECHO.=============================
)
IF DEFINED _Choice.!_Temp!.Value (
SET "_Choices=!_Choices!%%A"
CALL ECHO. %%A : %%_Choice.!_Temp!.Value%%
)
)
)
ECHO.
CHOICE /C !_Choices! /N /M "What File do you want to choose? "
SET "_Chosen=%ERRORLEVEL%"
SET "%~1=!_Choice.%_Chosen%.Value!"
GOTO :EOF
)
You can try this:
#echo off
setlocal enabledelayedexpansion
set jim=
for /f "delims=] tokens=1*" %%a in ('find /v /n "" ^<list.txt') do (
set jim=!jim! %%b
)
echo Select a file from !jim!
set /p file=
type !file!
pause
This will read all lines from the list.txt and return them within a variable !jim!.

Windows batch script to parse CSV file and output a text file

I've seen a response on another page (Help in writing a batch script to parse CSV file and output a text file) - brilliant code BTW:
#ECHO OFF
IF "%~1"=="" GOTO :EOF
SET "filename=%~1"
SET fcount=0
SET linenum=0
FOR /F "usebackq tokens=1-10 delims=," %%a IN ("%filename%") DO ^
CALL :process "%%a" "%%b" "%%c" "%%d" "%%e" "%%f" "%%g" "%%h" "%%i" "%%j"
GOTO :EOF
:trim
SET "tmp=%~1"
:trimlead
IF NOT "%tmp:~0,1%"==" " GOTO :EOF
SET "tmp=%tmp:~1%"
GOTO trimlead
:process
SET /A linenum+=1
IF "%linenum%"=="1" GOTO picknames
SET ind=0
:display
IF "%fcount%"=="%ind%" (ECHO.&GOTO :EOF)
SET /A ind+=1
CALL :trim %1
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO !f%ind%!!tmp!
ENDLOCAL
SHIFT
GOTO display
:picknames
IF %1=="" GOTO :EOF
CALL :trim %1
SET /a fcount+=1
SET "f%fcount%=%tmp%"
SHIFT
GOTO picknames
It works brilliantly for an example csv file I made in the format:
Header,Name,Place
one,two,three
four,five,six
However the actual file I want to change comprises of 64 fields - so I altered the tokens=1-10 to tokens=1-64 and increased the %%a etc right up to 64 variables (the last being called %%BL for example). Now, however, when I run the batch on my 'big' csv file (with the 64 tokens) nothing happens. No errors (good) but no output! (bad). If anyone can help that would be fantastic... am soooo close to getting the whole app working if I can just nail this last bit! Or if anyone has some example code that will do similar for an indefinite number of tokens... Ultimately I want to make a string which will be something like:
field7,field12,field15,field18
Important update - I don't think Windows batch is a good option for your needs because a single FOR /F cannot parse more than 31 tokens. See the bottom of the Addendum below for an explanation.
However, it is possible to do what you want with batch. This ugly code will give you access to all 64 tokens.
for /f "usebackq tokens=1-29* delims=," %%A in ("%filename%") do (
for /f "tokens=1-26* delims=," %%a in ("%%^") do (
for /f "tokens=1-9 delims=," %%1 in ("%%{") do (
rem Tokens 1-26 are in variables %%A - %%Z
rem Token 27 is in %%[
rem Token 28 is in %%\
rem Token 29 is in %%]
rem Tokens 30-55 are in %%a - %%z
rem Tokens 56-64 are in %%1 - %%9
)
)
)
The addendum provides important info on how the above works.
If you only need a few of the tokens spread out amongst the 64 on the line, then the solution is marginally easier in that you might be able to avoid using crazy characters as FOR variables. But there is still careful bookkeeping to be done.
For example, the following will give you access to tokens 5, 27, 46 and 64
for /f "usebackq tokens=5,27,30* delims=," %%A in ("%filename%") do (
for /f "tokens=16,30* delims=," %%E in ("%%D") do (
for /f "tokens=4 delims=," %%H in ("%%G") do (
rem Token 5 is in %%A
rem Token 27 is in %%B
rem Token 46 is in %%E
rem Token 64 is in %%H
)
)
)
April 2016 Update - Based on investigative work by DosTips users Aacini, penpen, and aGerman, I have developed a relatively easy method to simultaneously access thousands of tokens using FOR /F. The work is part of this DosTips thread. The actual code can be found in these 3 posts:
Work with a fixed number of columns
Work with varying numbers of columns
Dynamically choose which tokens to expand within the DO clause
Original Answer
FOR variables are limited to a single character, so your %%BL strategy can't work. The variables are case sensitive. According to Microsoft you are limited to capturing 26 tokens within one FOR statement, but it is possible to get more if you use more than just alpha. Its a pain because you need an ASCII table to figure out which characters go where. FOR does not allow just any character however, and the maximum number of tokens that a single FOR /F can assign is 31 +1. Any attempt to parse and assign more than 31 will quietly fail, as you have discovered.
Thankfully, I don't think you need that many tokens. You simply specify which tokens you want with the TOKENS option.
for /f "usebackq tokens=7,12,15,18 delims=," %%A in ("%filename%") do echo %%A,%%B,%%C,%%D
will give you your 7th, 12th, 15th and 18th tokens.
Addendum
April 2016 Update A couple weeks ago I learned that the following rules (written 6 years ago) are code page dependent. The data below has been verified for code pages 437 and 850. More importantly, the FOR variable sequence of extended ASCII characters 128-254 does not match the byte code value, and varies tremendously by code page. It turns out the FOR /F variable mapping is based on the underlying UTF-(16?) code point. So the extended ASCII characters are of limited use when used with FOR /F. See the thread at http://www.dostips.com/forum/viewtopic.php?f=3&t=7703 for more information.
I performed some tests, and can report the following (updated in response to jeb's comment):
Most characters can be used as a FOR variable, including extended ASCII 128-254. But some characters cannot be used to define a variable in the first part of a FOR statement, but can be used in the DO clause. A few can't be used for either. Some have no restrictions, but require special syntax.
The following is a summary of characters that have restrictions or require special syntax. Note that text within angle brackets like <space> represents a single character.
Dec Hex Character Define Access
0 0x00 <nul> No No
09 0x09 <tab> No %%^<tab> or "%%<tab>"
10 0x0A <LF> No %%^<CR><LF><CR><LF> or %%^<LF><LF>
11 0x0B <VT> No %%<VT>
12 0x0C <FF> No %%<FF>
13 0x0D <CR> No No
26 0x1A <SUB> %%%VAR% %%%VAR% (%VAR% must be defined as <SUB>)
32 0x20 <space> No %%^<space> or "%%<space>"
34 0x22 " %%^" %%" or %%^"
36 0x24 $ %%$ %%$ works, but %%~$ does not
37 0x25 % %%%% %%~%%
38 0x26 & %%^& %%^& or "%%&"
41 0x29 ) %%^) %%^) or "%%)"
44 0x2C , No %%^, or "%%,"
59 0x3B ; No %%^; or "%%;"
60 0x3C < %%^< %%^< or "%%<"
61 0x3D = No %%^= or "%%="
62 0x3E > %%^> %%^> or "%%>"
94 0x5E ^ %%^^ %%^^ or "%%^"
124 0x7C | %%^| %%^| or "%%|"
126 0x7E ~ %%~ %%~~ (%%~ may crash CMD.EXE if at end of line)
255 0xFF <NB space> No No
Special characters like ^ < > | & must be either escaped or quoted. For example, the following works:
for /f %%^< in ("OK") do echo "%%<" %%^<
Some characters cannot be used to define a FOR variable. For example, the following gives a syntax error:
for /f %%^= in ("No can do") do echo anything
But %%= can be implicitly defined by using the TOKENS option, and the value accessed in the DO clause like so:
for /f "tokens=1-3" %%^< in ("A B C") do echo %%^< %%^= %%^>
The % is odd - You can define a FOR variable using %%%%. But The value cannot be accessed unless you use the ~ modifier. This means enclosing quotes cannot be preserved.
for /f "usebackq tokens=1,2" %%%% in ('"A"') do echo %%%% %%~%%
The above yields %% A
The ~ is a potentially dangerous FOR variable. If you attempt to access the variable using %%~ at the end of a line, you can get unpredictable results, and may even crash CMD.EXE! The only reliable way to access it without restrictions is to use %%~~, which of course strips any enclosing quotes.
for /f %%~ in ("A") do echo This can crash because its the end of line: %%~
for /f %%~ in ("A") do echo But this (%%~) should be safe
for /f %%~ in ("A") do echo This works even at end of line: %%~~
The <SUB> (0x1A) character is special because <SUB> literals embedded within batch scripts are read as linefeeds (<LF>). In order to use <SUB> as a FOR variable, the value must be somehow stored within an environment variable, and then %%%VAR% will work for both definition and access.
As already stated, a single FOR /F can parse and assign a maximum of 31 tokens. For example:
#echo off
setlocal enableDelayedExpansion
set "str="
for /l %%n in (1 1 35) do set "str=!str! %%n"
for /f "tokens=1-31" %%A in ("!str!") do echo A=%%A _=%%_
The above yields A=1 _=31 Note - tokens 2-30 work just fine, I just wanted a small example
Any attempt to parse and assign more than 31 tokens will silently fail without setting ERRORLEVEL.
#echo off
setlocal enableDelayedExpansion
set "str="
for /l %%n in (1 1 35) do set "str=!str! %%n"
for /f "tokens=1-32" %%A in ("!str!") do echo this example fails entirely
You can parse and assign up to 31 tokens and assign the remainder to another token as follows:
#echo off
setlocal enableDelayedExpansion
set "str="
for /l %%0 in (1 1 35) do set "str=!str! %%n"
for /f "tokens=1-31*" %%# in ("!str!") do echo #=%%A ^^=%%^^ _=%%_
The above yields #=1 ^=31 _=32 33 34 35
And now for the really bad news. A single FOR /F can never parse more than 31 tokens, as I learned when I looked at Number of tokens limit in a FOR command in a Windows batch script
#echo off
setlocal enableDelayedExpansion
set "str="
for /l %%n in (1 1 35) do set "str=!str! %%n"
for /f "tokens=1,31,32" %%A in ("!str!") do echo A=%%A B=%%B C=%%C
The very unfortunate output is A=1 B=31 C=%C
My answer is comprised of two parts. The first one is a new answer I posted in help-in-writing-a-batch-script-to-parse-csv-file-and-output-a-text-file question that have not any limit in the number of fields.
The second part is a modification to that answer that allows to select which fields will be extracted from the csv file by additional parameters placed after the file name. The modified code is in UPPERCASE LETTERS.
#echo off
setlocal EnableDelayedExpansion
rem Create heading array:
set /P headingRow=< %1
set i=0
for %%h in (%headingRow%) do (
set /A i+=1
set heading[!i!]=%%~h
)
REM SAVE FILE NAME AND CREATE TARGET ELEMENTS ARRAY:
SET FILENAME=%1
IF "%2" == "" (FOR /L %%J IN (1,1,%i%) DO SET TARGET[%%J]=%%J) & GOTO CONTINUE
SET J=0
:NEXTTARGET
SHIFT
IF "%1" == "" GOTO CONTINUE
SET /A J+=1
SET TARGET[%J%]=%1
GOTO NEXTTARGET
:CONTINUE
rem Process the file:
call :ProcessFile < %FILENAME%
exit /B
:ProcessFile
set /P line=
:nextLine
set line=:EOF
set /P line=
if "!line!" == ":EOF" goto :EOF
set i=0
SET J=1
for %%e in (%line%) do (
set /A i+=1
FOR %%J IN (!J!) DO SET TARGET=!TARGET[%%J]!
IF !i! == !TARGET! (
for %%i in (!i!) do echo !heading[%%i]!%%~e
SET /A J+=1
)
)
goto nextLine
exit /B
For example:
EXTRACTCSVFIELDS THEFILE.CSV 7 12 15 18
EDIT A simpler method
Below is a new version that is both simpler and easier to understand because it use a list of target elements instead of an array:
#echo off
setlocal EnableDelayedExpansion
rem Create heading array:
set /P headingRow=< %1
set i=0
for %%h in (%headingRow%) do (
set /A i+=1
set heading[!i!]=%%~h
)
REM CREATE TARGET ELEMENTS LIST:
IF "%2" == "" (
SET TARGETLIST=
FOR /L %%J IN (1,1,%i%) DO SET TARGETLIST=!TARGETLIST! %%J
) ELSE (
SET TARGETLIST=%*
SET TARGETLIST=!TARGETLIST:* =!
)
rem Process the file:
call :ProcessFile < %1
exit /B
:ProcessFile
set /P line=
:nextLine
set line=:EOF
set /P line=
if "!line!" == ":EOF" goto :EOF
set i=0
for %%e in (%line%) do (
set /A i+=1
for %%i IN (!i!) DO (
IF "!TARGETLIST:%%i=!" NEQ "!TARGETLIST!" (
echo !heading[%%i]!%%~e
)
)
)
goto nextLine
exit /B
Also, this version does not require the desired fields be given in order.
EDIT
Oops! The for parameters stuff distracted my attention, so I was not aware of your last request:
"Ultimately I want to make a string which will be something like:
field7,field12,field15,field18"
Just modify the last part of the program to do that:
:ProcessFile
set /P line=
:nextLine
set line=:EOF
set /P line=
if "!line!" == ":EOF" goto :EOF
set i=0
set resultString=
for %%e in (%line%) do (
set /A i+=1
for %%i IN (!i!) DO (
IF "!TARGETLIST:%%i=!" NEQ "!TARGETLIST!" (
set resultString=!resultString!%%~e,
)
)
)
set resultString=%resultString:~0,-1%
echo Process here the "%resultString%"
goto nextLine
exit /B
You may also remove the creation of the heading array, because you want NOT the headings! ;)
Using %%# and %%` (not documented here) as start variables the max you can get is 71:
#echo off
for /f "tokens=1-31* delims=," %%# in ("%filename%") do (
echo:
echo 1=%%#
echo 2=%%A
echo 3=%%B
echo 4=%%C
echo 5=%%D
echo 6=%%E
echo 7=%%F
echo 8=%%G
echo 9=%%H
echo 10=%%I
echo 11=%%J
echo 12=%%K
echo 13=%%L
echo 14=%%M
echo 15=%%N
echo 16=%%O
echo 17=%%P
echo 18=%%Q
echo 19=%%R
echo 20=%%S
echo 21=%%T
echo 22=%%U
echo 23=%%V
echo 24=%%W
echo 25=%%X
echo 26=%%Y
echo 27=%%Z
echo 28=%%[
echo 29=%%\
echo 30=%%]
echo 31=%%^^
for /F "tokens=1-30* delims=," %%` in ("%%_") do (
echo 32=%%`
echo 33=%%a
echo 34=%%b
echo 35=%%c
echo 36=%%d
echo 37=%%e
echo 38=%%f
echo 39=%%g
echo 40=%%h
echo 41=%%i
echo 42=%%j
echo 43=%%k
echo 44=%%l
echo 45=%%m
echo 46=%%n
echo 47=%%o
echo 48=%%p
echo 49=%%q
echo 50=%%r
echo 51=%%s
echo 52=%%t
echo 53=%%u
echo 54=%%v
echo 55=%%w
echo 56=%%x
echo 57=%%y
echo 58=%%z
echo 59=%%{
echo 60=%%^|
echo 61=%%}
for /F "tokens=1-9* delims=," %%0 in ("%%~") do (
echo 62=%%0
echo 63=%%1
echo 64=%%2
echo 65=%%3
echo 66=%%4
echo 67=%%5
echo 68=%%6
echo 69=%%7
echo 70=%%8
echo 71=%%9
)
)
)
When I read this problem again and the solution proposed in the most-voted answer, I thought that a much simpler way to make good use of a series of nested FOR /F commands could be developed. I started to write such a method, that would allowed to use 127 additional tokens placing they in the ASCII 128-254 characters range. However, when my program was completed I discovered that the ASCII characters in the "natural" 128..254 order could not be used for this purpose...
Then, a group of people were interested in this problem and they made a series of discoveries and developments that culminated in a method that allows to use many tokens (more than 43,000!) in a series of nested FOR /F commands. You may read a detailed description of the research and development involved in this discovery at this DosTips topic.
Finally, I used the new method to modify my program, so it now allows the processing of up to 4094 simultaneous tokens (from a text file with long lines), but in a simple way. My application consists in a Batch file, called MakeForTokens.bat, that you may run with the number of desired tokens in the parameter. For example:
MakeForTokens.bat 64
The program generates a Batch file, called ForTokens.bat, that contain all the code necessary to manage such an amount of simultaneous tokens, including examples of how to process a file. In this way, the users just needs to insert their own file names and desired tokens in order to get a working program.
In this particular case, this would be the final ForTokens.bat file that solve the problem as stated in this question, after most descriptive comments were deleted:
#echo off & setlocal EnableDelayedExpansion & set "$numTokens=65"
Rem/For Step 1: Define the series of auxiliary variables that will be used as FOR tokens.
call :DefineForTokens
Rem/For Step 2: Define an auxiliary variable that will contain the desired tokens when it is %expanded%.
call :ExpandTokensString "tokens=7,12,15,18"
Rem/For Step 3: Define the variable with the "delims" value that will be used in the nested FOR's.
set "delims=delims=,"
Rem/For Step 4: Create the macro that contain the nested FOR's.
call :CreateNestedFors
Rem/For Step 5: This is the main FOR /F command that process the file.
for /F "usebackq tokens=1-31* %delims%" %%%$1% in ("filename.txt") do %NestedFors% (
Rem/For Step 6: Process the tokens.
Rem/For To just show they, use the "tokens" variable defined above:
echo %tokens%
Rem/For You may also process individual tokens via another FOR /F command:
for /F "tokens=1-%tokens.len%" %%a in ("%tokens%") do (
echo Field #7: %%a
echo Field #12: %%b
echo Field #15: %%c
echo Field #18: %%d
)
)
goto :EOF
Support subroutines. You must not modify any code below this line.
:DefineForTokens
for /F "tokens=2 delims=:." %%p in ('chcp') do set /A "_cp=%%p, _pages=($numTokens/256+1)*2"
set "_hex= 0 1 2 3 4 5 6 7 8 9 A B C D E F"
call set "_pages=%%_hex:~0,%_pages%%%"
if %$numTokens% gtr 2048 echo Creating FOR tokens variables, please wait . . .
(
echo FF FE
for %%P in (%_pages%) do for %%A in (%_hex%) do for %%B in (%_hex%) do echo %%A%%B 3%%P 0D 00 0A 00
) > "%temp%\forTokens.hex.txt"
certutil.exe -decodehex -f "%temp%\forTokens.hex.txt" "%temp%\forTokens.utf-16le.bom.txt" >NUL
chcp 65001 >NUL
type "%temp%\forTokens.utf-16le.bom.txt" > "%temp%\forTokens.utf8.txt"
(for /L %%N in (0,1,%$numTokens%) do set /P "$%%N=") < "%temp%\forTokens.utf8.txt"
chcp %_cp% >NUL
del "%temp%\forTokens.*.txt"
for %%v in (_cp _hex _pages) do set "%%v="
exit /B
:CreateNestedFors
setlocal EnableDelayedExpansion
set /A "numTokens=$numTokens-1, mod=numTokens%%31, i=numTokens/31, lim=31"
if %mod% equ 0 set "mod=31"
set "NestedFors="
for /L %%i in (32,31,%numTokens%) do (
if !i! equ 1 set "lim=!mod!"
set "NestedFors=!NestedFors! for /F "tokens=1-!lim!* %delims%" %%!$%%i! in ("%%!$%%i!") do"
set /A "i-=1"
)
for /F "delims=" %%a in ("!NestedFors!") do endlocal & set "NestedFors=%%a"
exit /B
:ExpandTokensString variable=tokens definitions ...
setlocal EnableDelayedExpansion
set "var=" & set "tokens=" & set "len=0"
if "%~2" equ "" (set "params=%~1") else set "params=%*"
for %%a in (!params!) do (
if not defined var (
set "var=%%a"
) else for /F "tokens=1-3 delims=-+" %%i in ("%%a") do (
if "%%j" equ "" (
if %%i lss %$numTokens% set "tokens=!tokens! %%!$%%i!" & set /A len+=1
) else (
if "%%k" equ "" (set "k=1") else set "k=%%k"
if %%i leq %%j (
for /L %%n in (%%i,!k!,%%j) do if %%n lss %$numTokens% set "tokens=!tokens! %%!$%%n!" & set /A len+=1
) else (
for /L %%n in (%%i,-!k!,%%j) do if %%n lss %$numTokens% set "tokens=!tokens! %%!$%%n!" & set /A len+=1
)
)
)
)
endlocal & set "%var%=%tokens%" & set "%var%.len=%len%"
exit /B
You may download the MakeForTokens.bat application from this site.

Help in writing a batch script to parse CSV file and output a text file

I am struggling to write a batch script which can read a CSV file such as below
Name:, City:, Country:
Mark, London, UK
Ben, Paris, France
Tom, Athens, Greece
There will be a heading row in the CSV file. It should output to a text file as below:
Name:Mark
City:London
Country:UK
Name:Ben
City:Paris
Country:France
Name:Tom
City:Athens
Country:Greece
The field separator (:) in the above output is expected to be provided in the header row itself. So all that I need to do is concatenate the field heading and its value.
The number of columns in this CSV file is not fixed, so the script should not limit to 3 tokens. Kindly help!
#ECHO OFF
IF "%~1"=="" GOTO :EOF
SET "filename=%~1"
SET fcount=0
SET linenum=0
FOR /F "usebackq tokens=1-10 delims=," %%a IN ("%filename%") DO ^
CALL :process "%%a" "%%b" "%%c" "%%d" "%%e" "%%f" "%%g" "%%h" "%%i" "%%j"
GOTO :EOF
:trim
SET "tmp=%~1"
:trimlead
IF NOT "%tmp:~0,1%"==" " GOTO :EOF
SET "tmp=%tmp:~1%"
GOTO trimlead
:process
SET /A linenum+=1
IF "%linenum%"=="1" GOTO picknames
SET ind=0
:display
IF "%fcount%"=="%ind%" (ECHO.&GOTO :EOF)
SET /A ind+=1
CALL :trim %1
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO !f%ind%!!tmp!
ENDLOCAL
SHIFT
GOTO display
:picknames
IF %1=="" GOTO :EOF
CALL :trim %1
SET /a fcount+=1
SET "f%fcount%=%tmp%"
SHIFT
GOTO picknames
This batch scipt:
accepts one parameter, the name of the file to process;
does not verify the presence of : at the end of a header token, and when the values are displayed they are placed immediately after the corresponding header tokens;
trims all the leading spaces (but not the trailing ones);
considers the first row to be the header row, which also defines the number of tokens to process in subsequent rows;
supports up to 10 tokens, and the two areas highlighted in bold italics are responsible for that (so when you need to change the maximum number, modify both areas: if you increase the number, you must expand the "%%a" "%%b" "%%c" … list, and, likewise, if you decrease the number, then shrink the list).
I know this is an old question, but this type of question is my favorite one so here it is my answer:
#echo off
setlocal EnableDelayedExpansion
rem Create heading array:
set /P headingRow=< %1
set i=0
for %%h in (%headingRow%) do (
set /A i+=1
set heading[!i!]=%%~h
)
rem Process the file:
call :ProcessFile < %1
exit /B
:ProcessFile
set /P line=
:nextLine
set line=:EOF
set /P line=
if "!line!" == ":EOF" goto :EOF
set i=0
for %%e in (%line%) do (
set /A i+=1
for %%i in (!i!) do echo !heading[%%i]!%%~e
)
goto nextLine
exit /B
This program have not any limit in the number of fields. This version requires to enclose in quotes the elements that may have spaces or other Batch delimiters, but this restriction may be easily fixed.
Python makes this so easy it should be regulated by the government.
from csv import DictReader
with open('file', 'rb') as file:
reader = DictReader(file)
for line in reader:
for field in reader.fieldnames:
print '{0}{1}'.format(field.strip(), line[field].strip())
print '\n'
Edit: I guess you need something native to the Windows command shell. Oh well.

Resources