Batch: Iterate .csv file columns - windows

I have this .csv file:
col0,col1,col2,col3,col4
a,1,10,100,1000
b,2,11,101,1001
c,3,12,102,1002
d,4,13,103,1003
e,5,14,105,1004
I need to iterate each column in the .csv without knowing the number of columns.
First column is skipped because is not needed.
I have this code so far, but I need a solution for the case where I don't know the number of columns.
I need the value of each column in a later step where I calculate something.
#echo off
setlocal enableDelayedExpansion
:: set workspace data
set INPUT_FILE_LOCATION=D:\Scripts\
set CSV_FILE_NAME=test.csv
pushd %INPUT_FILE_LOCATION%
::loop through the csv file
for /F "tokens=2,3,4,5 delims=," %%i in (%CSV_FILE_NAME%) do (
echo %%i,%%j,%%k,%%l
rem echo.%%~i^|END
)
endlocal
To be more specific, I have a .csv file, with some columns and many many rows. Starting with the second column, I will need to make the difference of every two elements of every column to verify if there is at least one difference greater than 1.(The values on columns are going to be in ascending order, so as an example using the csv above, the code should do the following: starting on col1, verify if 2-1 > 1, then if 3-2 > 1, then if 4-3 > 1 then 5-4 > 1, then it should verify the same thing for the next column(col2) and so on, until we reach the last column. If I will find one difference greater than 1, I want to print a message that "a bigger difference was found on" the header of that column where the bigger difference was found; Somehow I want to localize in which column was found the unexpected difference by using the title of the column from the header; for example, in col3, we have a difference greater than 1 and I want to print "there's a difference greater than 1 in col3", where col3 is in the header). In time, I will need to add some more columns, so the file could have 30 or 40 columns with the same structure like the previous ones.

#ECHO OFF
SETLOCAL
rem The following settings for the source directory, filenames are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
SET "filename1=%sourcedir%\q71308045.txt"
:: comma-separated list of columns to ignore
SET "ignorecolumns=1"
:: remove all 'cell' variables from environment
For %%b IN (cell) DO FOR /F "delims==" %%c In ('set %%b 2^>Nul') DO SET "%%c="
SET /a rowcount=0
SET /a maxcolumns=0
rem usebackq should be omitted if the source filename is not quoted
rem skip=1 skips the first (header) line. Omit to skip no lines
FOR /f "usebackq skip=1 delims=" %%b IN ("%filename1%") DO (
CALL :process %%b
CALL :linebyline
)
ECHO %rowcount% rows, maximum %maxcolumns% columns
SET cell
GOTO :EOF
:process
SET /a rowcount+=1
SET /a columns=0
:procloop
IF "%~1"=="" GOTO :eof
SET /a columns+=1
IF DEFINED ignorecolumns FOR %%c IN (%ignorecolumns%) DO IF %columns%==%%c GOTO donecolumn
SET "cell[%rowcount%,%columns%]=%~1"
:donecolumn
IF %columns% gtr %maxcolumns% SET /a maxcolumns=columns
SET /a cellsinrow[%rowcount%]=%columns%
SHIFT
GOTO procloop
GOTO :eof
:: processing line-by-line if required
:linebyline
ECHO row %rowcount% has %columns% columns
GOTO :eof
Here's a generalised solution, in the absence of specifics.
Note that it does not cater for empty columns.
Each line is presented to :process as a parameter. :process counts each column and inserts it into the cells wired-array omitting any columns not required and tracks the maximum column-number found and the number of cells in each row.
Environment space is limited AFAIAA, so compensatory measures are needed if huge amounts of data are processed.
The :linebyline routine is executed for each row, so if the required processing does not need cells then rowcount could be set back to 0 within this routine, having the effect of reporting cells[1,*] for each line
--- revision following clarification
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following settings for the source directory, filenames are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
SET "filename1=%sourcedir%\q71308045.txt"
:: comma-separated list of columns to ignore
SET "ignorecolumns=1"
:: remove all 'cell' variables from environment
For %%b IN (cell) DO FOR /F "delims==" %%c In ('set %%b 2^>Nul') DO SET "%%c="
SET /a rowcount=0
SET /a maxcolumns=0
rem usebackq should be omitted if the source filename is not quoted
rem skip=1 skips the first (header) line. Omit to skip no lines
FOR /f "usebackq delims=" %%b IN ("%filename1%") DO (
CALL :process %%b
CALL :linebyline
)
rem ECHO %rowcount% rows, maximum %maxcolumns% columns
rem SET cell
GOTO :EOF
:process
SET /a rowcount+=1
SET /a columns=0
:procloop
IF "%~1"=="" GOTO :eof
SET /a columns+=1
IF DEFINED ignorecolumns FOR %%c IN (%ignorecolumns%) DO IF %columns%==%%c GOTO donecolumn
SET "cell[%rowcount%,%columns%]=%~1"
:donecolumn
IF %columns% gtr %maxcolumns% SET /a maxcolumns=columns
SET /a cellsinrow[%rowcount%]=%columns%
SHIFT
GOTO procloop
GOTO :eof
:: processing line-by-line if required
:linebyline
:: if rowcount=1 then column names are in cell[1,*] and nothing to do
:: if rowcount=2 then we have the starting data row and nothing to do
IF %rowcount% lss 3 GOTO :eof
:: Now we can compare row 2 to row %rowcount%
FOR /L %%c IN (1,1,%maxcolumns%) DO IF "!cell[2,%%c]!" neq "" CALL :matchcells %%c
:: And move row %rowcount% to row 2; removing row %rowcount% from environment
FOR /L %%c IN (1,1,%maxcolumns%) DO IF "!cell[2,%%c]!" neq "" SET cell[2,%%c]=!cell[%rowcount%,%%c]!&SET "cell[%rowcount%,%%c]="
GOTO :eof
:: Match cell[2,%1] to cell[%rowcount%,%1]
:matchcells
SET /a celldiff = !cell[%rowcount%,%1]! - !cell[2,%1]!
IF %celldiff% == 1 GOTO :eof
ECHO row %rowcount% column %1 [!cell[1,%1]!] value difference = %celldiff%
GOTO :eof
Well, very little difference here. I still believe that the specification is faulty, since if you know the first data row, then you know what every following data row should be as each column in each succeeding row should be one more than the value in the row prior. Therefore you only need one row of data as you can generate the remaining rows and don't need to go through a generate/verify cycle.

Related

Nested FOR loops and variables in Batch

I have the following code;
setlocal EnableDelayedExpansion
:splitEncode
::Get the number of Chapters
set "cmd=FINDSTR /R /N "^.*" %~n1.txt | FIND /C ":""
for /F %%a in ('!cmd!') do set numChapters=%%a
::Cycle through this once for every chapter, getting the line and the line after it
for /L %%a in (1,1,%numChapters%) do (
set "skip="
if %%a geq 2 (
set /a skip=%%a-1
set "skip=skip=!skip!"
)
for /F "!skip! tokens=1,2" %%i in ("%~n1.txt") do (
set startTime=%%i
set chapterName=%%j
)
set "skip=skip=%%a"
for /F !skip! %%i in ("%~n1.txt") do (
set endTime=%%i
)
echo %startTime% %endTime% %chapterName%
)
First I find out how many lines are in a text file, and set that to the variable numChapters.
I then use this to create a for loop that iterates for each chapter.
Inside the for loop, there are two further loops. The first reads a line, and the second reads the following line.
The intent of this is to read lines 1+2, 2+3, 3+4, and use those values as part of a command run the same number of times as the number of lines.
This means that from a list such as this;
00:00:00 The Meeting Room/The Meeting
00:03:36 Long Distance Runaround
00:07:47 Wonderous Stories
I can end up with a command that includes the start time, end time, and chapter title.
The issue I am facing is that no matter what I do, I cannot get the nested for loops to use the skip variables. I've tried %%a, %skip%, !skip!, and none of them work. The value isn't correctly substituted in any situation.
Does anyone have any way to get this variable used, or a better method of reading a specific line of a text file than a for loop?
The option string of for /F (like the root path of for /R) requires immediate (%-)expansion, because for (besides if and rem) is recognised by the command interpreter even before delayed expansion and also expansion of for meta-variables occur.
A possible solution is to put each for /F loop with the dynamic skip options into a sub-routine, to use call to call it and to apply %-expansion therein (see all the additional rem remarks for explanations):
#echo off
setlocal EnableDelayedExpansion
:splitEncode
rem Get the number of chapters
rem // To determine the number of lines in a file you do not need `findstr`:
for /F %%a in ('^< "%~n1.txt" find /C /V ""') do set "numChapters=%%a"
rem Cycle through this once for every chapter, getting the line and the line after it
for /L %%a in (1,1,%numChapters%) do (
set /A "skip=%%a-1"
call :getTwoValues startTime chapterName "%~n1.txt" "!skip!"
call :getTwoValues endTime dummy "%~n1.txt" "%%a"
rem /* For the last line, there is of course no next line containing the end time;
rem therefore, let us mark that case specifically: */
if not defined endTime set "endTime=??:??:??"
rem /* If there is no chapter specified, do not output anything; this might also
rem be quite useful in case the last line just contains a time stamp but no
rem chapter name just to provide the end time of the last one: */
if defined chapterName echo !startTime! !endTime! !chapterName!
)
goto :EOF
:getTwoValues <1st var. name> <2nd var. name> <file path/name> <lines to skip>
rem // Ensure not to return the former output, and set up `skip` option string:
set "%~1=" & set "%~2=" & set /A "skip=0, skip+=%~4" 2> nul
if %skip% gtr 0 (set "skip=skip=%skip%") else set "skip="
rem /* Added `usebackq` in order not to interprete the quoted file path/name as
rem a literal string; also changed the `tokens` option to return the first
rem token and then the whole remainder of the line: */
rem /* Remember that `for /F` regards empty lines for its `skip` option, but it
rem does not iterate through such; hence the first line it iterates over is
rem actually the first non-empty line after the number of skipped lines: */
for /F "usebackq %skip% tokens=1,*" %%i in ("%~3") do (
set "%~1=%%i"
set "%~2=%%j"
rem // Since we do not want to iterate to the last line, leave the loop here:
goto :EOF
)
rem /* This is just needed in case `skip` points beyond the end of the file, or
rem there are no more non-empty lines behind the skipped ones: */
goto :EOF
Based on your sample data, the output should be this:
00:00:00 00:03:36 The Meeting Room/The Meeting
00:03:36 00:07:47 Long Distance Runaround
00:07:47 ??:??:?? Wonderous Stories
However, the entire approach could be heavily simplified, when you avoid the file multiple times and do not read each line twice, by simply reading the file line by line once, but return the chapter information from the previous iteration, together with the end time from the current line:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=%~n1.txt" & rem // (path/name of file to process)
set "_FRMT=??:??:??" & rem // (dummy end time output for last chapter)
rem /* Initialise variables; loop through lines of files, augmented by
rem an additional line at the end, to alyways output last chapter: */
set "STA=" & for /F "tokens=1,*" %%K in ('
type "%_FILE%" ^& echo(%_FRMT%
') do (
rem // Output the chapter from the previous loop iteration:
set "END=%%K" & if defined STA if defined NAME (
setlocal EnableDelayedExpansion
echo(!STA! !END! !NAME!
endlocal
)
rem // Store chapter information for the next loop iteration:
set "STA=%%K" & set "NAME=%%L"
)
endlocal
exit /B

CMD - Pivot / Transpose lines of file [e.g. convert 1500 lines (x1 col) --> 500 lines (x3 col)]

how please can I 'pivot' or transpose a file (i.e. turn a single-column list, into a table of data)...
Currently...
VideoA.name
VideoA.size
VideoA.bitrate
VideoB.name
VideoB.size
VideoB.bitrate
...
Desired...
VideoA.name, VideoA.size, VideoA.bitrate
VideoB.name, VideoB.size, VideoB.bitrate
Name
Size
Bitrate
VideoA.name
VideoA.size
VideoA.bitrate
VideoB.name
VideoB.size
VideoB.bitrate
Extra Info / Context
I'm aware people often ask 'why are you doing this?' so (if interested), here is the wider context / problem I am trying to solve...
I have a list of files in Files.txt
I have a jscript batch file getProps.bat that extract file properties and prints them, 1 per line
I have written a batch file to loop through Files.txt, get the properties of each and write the output to Details.csv
However if I have 500 files x 3 properties, this currently gives me 1500 lines, and I want 500 lines x 3 columns
GetProps_AllFiles.bat
---------------------
#ECHO OFF
SETLOCAL
FOR /F "tokens=*" %%A in (Files.txt) do (
getprops %%A 0,1,320 /noheaders >> Details.csv
)
Thanks in advance!
Use the "standard way" (for /f) to read a file line by line, extended by a counter. Add the line to a string (line), followed by a comma (or whatever you want to use as separator), and increase the counter. Except it's the third line. Then print the string plus the current line, reset the counter and string, and repeat.
#echo off
setlocal enabledelayedexpansion
set "line="
set count=0
(for /f "delims=" %%a in (test.txt) do (
set /a count+=1
if !count! equ 3 (
echo !line!%%a
set "line="
set count=0
) else (
set line=!line!%%a,
)
))>test.csv
The below is a slight adjustment to the code kindly provided by Stephan that allows a filename and number of lines to be passed into the script...
ColumiseFile.cmd
----------------
#ECHO OFF
SETLOCAL enabledelayedexpansion
REM :: USAGE -----------------------------------------------------------------
REM ColumiseFile [1]File.txt [2]NumOfLines
REM > Concatenates every n Lines into 1, exporting result to File.csv
SET "File=%1"
SET /A Lines=%2
REM :: MAIN -------------------------------------------------------------------
REM Thanks to Stephan [https://stackoverflow.com/a/67664755/15919675]
REM Loops through input file, compacting every n lines into 1
set "line="
set count=0
(for /f "delims=" %%a in (%File%) do (
set /a count+=1
if !count! equ %Lines% (
echo !line!%%a
set "line="
set count=0
) else (
set line=!line!%%a,
)
REM :: OUTPUT -----------------------------------------------------------------
REM Create .csv in same location as source .txt file
))>%~dpn1.csv

sorting files according to keywords, need a more database-y solution

I'm making a script that sorts video files into folders by checking for known keywords in the file. As the amount of keywords grows out of control the script becomes very slow, taking several seconds for each file to be processed.
#echo off
cd /d d:\videos\shorts
if /i not "%cd%"=="d:\videos\shorts" echo invalid shorts dir. && exit /b
:: auto detect folder name via anchor file
for /r %%i in (*spirit*science*chakras*) do set conspiracies=%%~dpi
if not exist "%conspiracies%" echo conscpiracies dir missing. && pause && exit /b
for /r %%i in (*modeselektor*evil*) do set musicvideos=%%~dpi
if not exist "%musicvideos%" echo musicvideos dir missing. && pause && exit /b
for %%s in (*) do set "file=%%~nxs" & set "full=%%s" & call :count
for %%v in (*) do echo can't sort "%%~nv"
exit /b
:count
set oldfile="%file%"
set newfile=%oldfile:&=and%
if not %oldfile%==%newfile% ren "%full%" %newfile%
set count=0
set words= & rem
echo "%~n1" | findstr /i /c:"music" >nul && set words=%words%, music&& set /a count+=1
echo "%~n1" | findstr /i /c:"official video" >nul && set words=%words%, official video&& set /a count+=2
set words=%words:has, =has %
set words=%words: , =%
if not %count%==0 echo "%file%" has "%words%" %count%p for music videos
set musicvideoscount=%count%
set count=0
set words= & rem
echo "%~n1" | findstr /i /c:"misinform" >nul && set words=%words%, misinform&& set /a count+=1
echo "%~n1" | findstr /i /c:"antikythera" >nul && set words=%words%, antikythera&& set /a count+=2
set words=%words:has, =has %
set words=%words: , =%
if not %count%==0 echo "%file%" has "%words%" %count%p for conspiracies
set conspiraciescount=%count%
set wanted=3
set winner=none
:loop
:: count points and set winner (in case of tie lowest in this list wins, sort accordingly)
if %conspiraciescount%==%wanted% set winner=%conspiracies%
if %musicvideoscount%==%wanted% set winner=%musicvideos%
set /a wanted+=1
if not %wanted%==15 goto loop
if not "%winner%"=="none" move "%full%" "%winner%" >nul && echo "%winner%%file%" && echo.
Notice the "weight value" for each keyword. It counts the total points for each category, finds the largest value and moves the file to the folder appointed to that category. It also displays the words it has found and lastly lists files it finds unsortable so I can add keywords or tweak weight values.
I have stripped the amount of folders and keywords in this sample to bare minimum. The full script has six folders and 64k size with all the keywords (and growing).
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "tempfile=%temp%\somename"
SET "categories=music conspiracies"
REM SET "categories=conspiracies music"
(
FOR /f "tokens=1,2,*delims=," %%s IN (q45196316.txt) DO (
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*%%u*" 2^>nul'
) DO (
ECHO %%a^|%%s^|%%t
)
)
)>"%tempfile%"
SET "lastname="
FOR /f "tokens=1,2,*delims=|" %%a IN ('sort "%tempfile%"') DO (
CALL :resolve %%b %%c "%%a"
)
:: and the last entry...
CALL :resolve dummy 0
GOTO :EOF
:resolve
IF "%~3" equ "%lastname%" GOTO accum
:: report and reset accumulators
IF NOT DEFINED lastname GOTO RESET
SET "winner="
SET /a maxfound=0
FOR %%v IN (%categories%) DO (
FOR /f "tokens=1,2delims=$=" %%w IN ('set $%%v') DO CALL :compare %%w %%x
)
IF DEFINED winner ECHO %winner% %lastname:&=and%
:RESET
FOR %%v IN (%categories%) DO SET /a $%%v=0
SET "lastname=%~3"
:accum
SET /a $%1+=%2
GOTO :eof
:compare
IF %2 lss %maxfound% GOTO :EOF
IF %2 gtr %maxfound% GOTO setwinner
:: equal scores use categories to determine
IF DEFINED winner GOTO :eof
:Setwinner
SET "winner=%1"
SET maxfound=%2
GOTO :eof
You would need to change the setting of sourcedir to suit your circumstances.
I used a file named q45196316.txt containing this category data for my testing.
music,6,music
music,8,Official video
conspiracies,3,misinform
conspiracies,6,antikythera
missing,0,not appearing in this directory
I believe your problem is that repeatedly executing findstr is time-consuming.
This approach uses a data file containing lines of category,weight,mask. The categories variable contains a list of the categories in order of preference (for when the score is equal)
Read the data file, assigning category to %%s, weight to %%t and mask to %%u and then do a directory-scan using the mask. This will echo a line to the tempfile in the format name|category|weight for each name-match found. dir seems to be very fast after the first scan.
The resultant tempfile will thus have one line for each filename+category plus the weight, so if a filename fits into more than one category, more than one entry will be created.
We then scan a sorted version of that file and resolve the score.
First, if the filename changes, we can report on the last filename. This is done by comparing the values in the variables $categoryname. Since these are scanned in the order %categories% then the first category in the list is chosen if there is an equivalence of scores. The scores are then reset and lastname initialised to the new filename.
We then accumulate the score into $categoryname
So - I believe that will be a bit faster.
Revision
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "tempfile=%temp%\somename"
SET "categories="rock music" music conspiracies"
REM SET "categories=conspiracies music"
:: set up sorting categories
SET "sortingcategories="
FOR %%a IN (%categories%) DO SET "sortingcategories=!sortingcategories!,%%~a"
SET "sortingcategories=%sortingcategories: =_%"
:: Create "tempfile" containing lines of name|sortingcategory|weight
(
FOR /f "tokens=1,2,*delims=," %%s IN (q45196316.txt) DO (
SET "sortingcategory=%%s"
SET "sortingcategory=!sortingcategory: =_!"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*%%u*" 2^>nul'
) DO (
ECHO %%a^|!sortingcategory!^|%%t^|%%s^|%%u
)
)
)>"%tempfile%"
SET "lastname="
SORT "%tempfile%">"%tempfile%.s"
FOR /f "usebackqtokens=1,2,3delims=|" %%a IN ("%tempfile%.s") DO (
CALL :resolve %%b %%c "%%a"
)
:: and the last entry...
CALL :resolve dummy 0
GOTO :EOF
:: resolve by totalling weights (%2) in sortingcategories (%1)
:: for each name (%3)
:resolve
IF "%~3" equ "%lastname%" GOTO accum
:: report and reset accumulators
IF NOT DEFINED lastname GOTO RESET
SET "winner=none"
SET /a maxfound=0
FOR %%v IN (%sortingcategories%) DO (
FOR /f "tokens=1,2delims=$=" %%w IN ('set $%%v') DO IF %%x gtr !maxfound! (SET "winner=%%v"&SET /a maxfound=%%x)
)
ECHO %winner:_= % %lastname:&=and%
:RESET
FOR %%v IN (%sortingcategories%) DO SET /a $%%v=0
SET "lastname=%~3"
:accum
SET /a $%1+=%2
GOTO :eof
I've added a few significant comments.
You can now have spaces in category names - you need to quote the name (for reporting purposes) within the set catagories... statement.
sortingcategories is automatically derived - it's only used for sorting and is simply the categories with any space in a name replaced by an underscore.
In creating the tempfile, the category is processed to contain underscores (the sortingcategory) and when the final placement is resolved, the underscores are removed returning the category name.
Negative weights should now be processed appropriately.
-- further revision for "not append *"
FOR /f "tokens=1-5delims=," %%s IN (q45196316.txt) DO (
SET "sortingcategory=%%s"
SET "sortingcategory=!sortingcategory: =_!"
FOR %%z IN ("!sortingcategory!") DO (
SETLOCAL disabledelayedexpansion
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\%%~v%%u%%~w" 2^>nul'
AND
add 2 extra columns to the q45196316 file
music,6,music,*,*
music,8,Official video,"",*
conspiracies,3,misinform,*,*
conspiracies,6,kythera,*anti,*
missing,0,not appearing in this directory,*,*
rock music,2,metal,*,*
conspiracies,-5,negative,*,*
The for /f ... %%s now generates %%v and %%w containing the last two columns (as tokens is nor 1-5)
These are applied as prefix and suffix to %%u in the dir command. Note that "" should be used for nothing as two successive , are parsed as a single separator. The ~ before the v/w in %%~v means remove the quotes.

Joining a list of numbers in a .txt file with ,[space]

Hi I managed to get the code below to create a list of numbers and place a comma at the end of each number created However, it has proven to be quite the challenge to get them on the same line separated by a ,[space]
#ECHO OFF
setlocal EnableDelayedExpansion
set _Output=%UserProfile%\Desktop\NumGen.txt
::only change these three lines
set "start=1" ::starts from this number
set "amount=10" ::amount of files created
set "length=5" ::length of fileNames
set "join_with=, " ::what to join each number with
set /a "last=%start%+%amount%"
for /l %%i in (%start%,1,%last%) do (
set "folderName=0000000000%%i"
set "folderName=!folderName:~-%length%!%join_with%"
>>"%_Output%" ECHO.!folderName!
)
pause
so my output at the moment is
00001,
00002,
00003,
00004,
00005,
00006,
00007,
00008,
00009,
00010,
00011,
However I would like it to be
00001, 00002, 00003, 00004, 00005, 00006, 00007, 00008, 00009, 00010, 00011
I have windows 10 64bit. Any help will be appreciated
echo is not able to do that. But there is a workaround, (ab)using the set command:
for /l %%i in (1,1,5) do (
<nul set /p "=%%i, "
)
echo(
After a long break I decided to give this another bash and presto. Success :-)
::CallScript
#ECHO OFF
CALL :ScriptA
CALL :ScriptB
CALL :ScriptC
pause
goto :eof
:ScriptA
#ECHO OFF
setlocal EnableDelayedExpansion
set _Output=%UserProfile%\Desktop\NumGen.txt
::only change these three lines
set "start=1"
set "amount=10"
set "length=5"
set "join_with=, "
set /a "last=%start%+%amount%"
for /l %%i in (%start%,1,%last%) do (
set "folderName=0000000000%%i"
set "folderName=!folderName:~-%length%!%join_with%"
>>"%_Output%" ECHO.!folderName!
)
goto :eof
:ScriptB
#ECHO OFF
setlocal enableextensions enabledelayedexpansion
set "var="
for /f "usebackq delims=" %%a in ("%UserProfile%\Desktop\NumGen.txt") do set "var=!var!%%a"
echo %var%> "%UserProfile%\Desktop\NumList.txt"
goto :eof
:ScriptC
#ECHO OFF
del "%UserProfile%\Desktop\NumGen.txt"
goto :eof
This code determines the starting number, the amount of numbers, the length of the numbers and joins them with , "
Then ScriptB concatenates each number together with the , " and saves to NumList.txt
Lastly the script deletes the NumGen.txt file.
Stephan has one solution with SET /P. The other option is to build the entire string within an environment variable, and then write after the loop ends. This is significantly faster, but it will fail if the final string length exceeds ~8191 bytes.
set "str="
for /l %%i in (%start%,1,%last%) do (
set "folderName=0000000000%%i"
set "str=!str!!folderName:~-%length%!%join_with%"
)
>>"%_Output%" ECHO.!str!
You could remove the unwanted trailing , if you want:
>>"%_Output%" ECHO.!str:~0,-2!

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