Batch - Read contents of a file in an array - batch-file

I've a text file with two rows (say param.txt) which is shown below:
Mar2012
dim1,dim2,dim3,dim4
I want to read this file in batch and store the contents of first line in a variable called cube_name. When I'm reading the second line, I want to split the comma delimited string dim1,dim2,dim3,dim4 and create an array of four elements. I am planning to use the variable and the array in later part of the script.
The code which I created is shown below. The code is not working as expected.
#echo off & setlocal enableextensions enabledelayedexpansion
set /a count_=0
for /f "tokens=*" %%a in ('type param.txt') do (
set /a count_+=1
set my_arr[!count_!]=%%a
)
set /a count=0
for %%i in (%my_arr%) do (
set /a count+=1
if !count! EQU 1 (
set cube_name=%%i
)
if !count! GTR 1 (
set dim_arr=%%i:#=,%
)
)
for %%i in (%dim_arr%) do (
echo %%i
)
echo !cube_name!
I get to see the following when I run the code:
C:\Working folder>test2.bat
ECHO is off.
So this doesn't appear to work and I can't figure out what I'm doing wrong. I am fairly new to the batch scripting so help is appreciated

Your first FOR loop is OK. It is not how I would do it, but it works. Everything after that is a mess. It looks like you think arrays are a formal concept in batch, when they are not. It is possible to work with variables in a way that looks reminiscent of arrays. But true arrays do not exist within batch.
You use %my_arr% as if it is an array, but my_arr is not even defined. You have defined variables my_arr[1] amd my_arr[2] - the brackets and number are part of the variable name.
It also looks like you have a misunderstanding of FOR loops. I suggest you carefully read the FOR documentation (type HELP FOR from a command line). Also look at examples on this and other sites. The FOR command is very complicated because it has many variations that look similar to the untrained eye, yet have profoundly different behaviors. One excellent resource to help your understanding is http://judago.webs.com/batchforloops.htm
Assuming the file always has exactly 2 lines, I would solve your problem like so
#echo off
setlocal enableDelayedExpansion
set dimCnt=0
<param.txt (
set /p "cube_name=" >nul
set /p "dimList=" >nul
for %%D in (!dimList!) do (
set /a dimCnt+=1
set "dim[!dimCnt!]=%%D"
)
)
echo cube_name=!cube_name!
for /l %%I in (1 1 !dimCnt!) do echo dim[%%I]=!dim[%%I]!
One nice feature of the above solution is it allows for a varying number of terms in the list of dimensions in the 2nd line. It will fail if there are tabs, spaces, semicolon, equal, * or ? in the dimension names. There are relatively simple ways to get around this limitation if need be.
Tabs, spaces, semicolon and equal can be handled by using search and replace to enclose each term in quotes.
for %%D in ("!dimList:,=","!") do (
set /a dimCnt+=1
set "dim[!dimCnt!]=%%~D"
)
I won't post the full solution here since it is not likely to be needed. But handling * and/or ? would require replacing the commas with a new-line character and switching to a FOR /F statement.

I'm impressed of your code!
Do you try to debug or echo anything there?
You could simply add some echo's to see why your code can't work.
#echo off & setlocal enableextensions enabledelayedexpansion
set /a count_=0
for /f "tokens=*" %%a in ('type param.txt') do (
set /a count_+=1
set my_arr[!count_!]=%%a
)
echo ### show the variable(s) beginning with my_arr...
set my_arr
echo Part 2----
set /a count=0
echo The value of my_arr is "%my_arr%"
for %%i in (%my_arr%) do (
set /a count+=1
echo ## Count=!count!, content is %%i
if !count! EQU 1 (
set cube_name=%%i
)
if !count! GTR 1 (
echo ## Setting dim_arr to "%%i:#=,%"
set dim_arr=%%i:#=,%
echo
)
)
for %%i in (%dim_arr%) do (
echo the value of dim_arr is "%%i"
)
echo cube_name is "!cube_name!"
Output is
### show the variable(s) beginning with my_arr...
my_arr[1]=Mar2012
my_arr[2]=dim1,dim2,dim3,dim4
Part 2----
The value of my_arr is ""
cube_name is ""
As you can see your part2 fails completly.

Related

Problem with nested FOR-Loop and IF-Condition

I have some lines of text. Then I have a list with test-words. I like to look up each line of the text and check if one of the test-words appears in it.
Beforehand this works well with a commands like these:
IF not "!stringToTest:%searchstring%=!"=="!stringToTest!"
However, now this seems to be more complicated as I have nested Loops?
I try to create a little MWE for my problem:
#echo off
setlocal enabledelayedexpansion
set /a counterPC=0
set "listPC=Win10,Motherboard,USB-Port,Core"
FOR %%G in (%listPC%) do (
set PCsearchVal[!counterPC!]=%%G
set /a counterPC+=1
)
set /a counterPC-=1
set "dummyline=Environment,1234,ZUIOP,Core"
FOR %%G in (%dummyline%) do (
set "stringToTest=%%G"
echo String to Test: !stringToTest!
FOR /l %%I in (0,1,%counterPC%) do (
set "searchstring=!PCsearchVal[%%I]!"
echo Test for this String: !searchstring!
IF not "!stringToTest:%searchstring%=!"=="!stringToTest!" echo Searchstring is in String to Test
)
)
endlocal
pause
In this he alway enter the IF-Condition. I know that this can may be solved with FINDSTR however in all my other code I used the search-strategy liek above. There may be just a little mistake I oversee? Many thanks in advance-

Batch: using upper for loop parameter in lower one

The command im trying to run is:
::%items% is defined elsewhere and is the amount of items per line in the file
FOR /F "usebackq tokens=1-%items% delims=," %%1 IN (`TYPE %TextFile%`) DO (
FOR /l %%a in (%items%,-1,1) do (
set /a "number=%%a"
echo !number!
:: This is the main command I believe im having issues with
set word!number!=%%!number!
echo !word1!
echo !word2!
echo !word3!
)
set /a "lineused%randomline%=1"
goto exitloop
)
:exitloop
pause
Now what I'm trying to do is set the variable called wordX where X is the number of the token. Edit: Basically, trying to use the %% variabla from the upper for loop which the lower one is running inside of.
I could type all the lines of
set word1=%%1
set word2=%%2
set word3=%%3
but that would defeat the purpose of the versatile system I'm trying to build.
Format of the text file (%TextFile%) would simply be, in this case:
line1i1,line1i2
line2i1,line2i2
But I need for it to work also on for example:
line1i1,line1i2,line1i3,line1i4
line2i1,line2i2,line2i3,line1i4
Interesting idea, but that cannot work because FOR variable expansion takes place before delayed expansion. You need a method to get an extra round of FOR variable expansion.
You can CALL a subroutine, and then use a dummy FOR loop to re-establish a FOR context. FOR variables are global in scope as long as you are in a FOR loop context. So your subroutine can access a FOR variable that was defined earlier.
...
...
FOR /F "tokens=1-%items% delims=," %%1 IN ('TYPE %TextFile%') DO (
FOR /l %%a in (%items%,-1,1) do call :set %%a
echo !word1!
echo !word2!
echo !word3!
)
...
...
exit /b
:set
for %%. in (.) do set "word%1=%%%1"
exit /b
The above works, but I don't like it because CALLs are expensive (slow). This is typically not a problem when you only have a few CALLs. But in this case the CALL is in a tight loop - one for every column times the number of rows in the file. Ouch!
If you really want to parametize your SET statements, and you want decent performance, then you can define a dynamic "macro". Simply store the needed commands in a variable, and then execute the content of the variable within your loop.
Also note that the above is limited to 9 items (10 if you start with 0 instead of 1). It is easy to extend the supported item count to 26 if you use letters, and a lookup string.
Finally, your dynamic FOR is within some parenthesized block. Presumably your ITEMS is defined outside the block, otherwise %items% could not be used in the FOR /F definition. The SET macro must be expanded using regular expansion, so it should be defined at the same time ITEMS is defined - outside the outer loop.
set /a items=3
::Define SET macro
set "v= ABCDEFGHIJKLMNOPQRSTUVWXYZ"
set "set="
for /l %%N in (1 1 %items%) do set "set=!set!&set "word%%N=%%!v:~%%N,1!""
set "set=!set:~1!"
FOR ... some loop ... DO (
...
...
FOR /F "tokens=1-%items% delims=," %%A IN ('TYPE %TextFile%') DO (
%set%
echo !word1!
echo !word2!
echo !word3!
)
...
...
)
If the ITEMS variable must be set within the outer loop, then you must CALL out of the loop to establish the inner FOR /F loop.
Note that you dont need the TYPE to get contents of file, the FOR command itself can iterate over file content
You could try something like this:
#echo off
setlocal enabledelayedexpansion
set "TextFile=textfile.txt"
set /a "lineNum=0"
set /a "i=0"
for /f "tokens=*" %%a in (%TextFile%) do (
set /a lineNum=!line_num! + 1
set "line=%%a"
for %%b in ("!line:,=" "!") do (
set /a "i=!i!+1"
set /a "wordNum=!lineNum! * !i!
set "word!wordNum!=%%b"
)
)
echo !word1!
echo !word2!
echo !word3!
Given a file with the contents:
aaa,bbb,ccc,ddd
eee,fff,ggg,hhh
Output will be:
"aaa"
"bbb"
... and so on
To remove the quotes, use %%~b at the inner FOR at set "word!wordNum! line.
Also note that you don't even need to define the number of items per line!
Hope it helps,
Cheers!

set /A command expands with a comma for more than one var, only set does not

(set /a "m1=1,m2=2")
for /f %%c in ("%m1%%m2%") do echo %%c
pause
The brackets else where than due to the for command are used in cases, a space key should have been added.
The echo of the for command, is 12. I used the number characters to face the set /A command with decimal Expression.
When i try the same procedure only with a set for a Shell, may also be named m1 it is just possible without comma seperation.
With set command the m1 Expression would be 1 m2 2 and not two values like with a set /A SET.
Is there a way to use set only once and not only with the set /A?
As other answers and comments already indicated, there is no way to directly do this in one command, but via a procedure. The method below is the simplest one:
#echo off
rem Define the several values
set "vars=m1=1,m2=2"
rem Do it:
set "%vars:,=" & set "%"
echo m1=%m1%
echo m2=%m2%
You may remove the #echo off command and execute this program to see what exactly is executed...
As I understand the question you want something like:
set x=1,y=2
and as a result to have two variables (like set /a). The answer is no.
Though you can iterate trough expressions with plain for :
#echo off
for %%a in (
"x=1" "a=5"
"y=2" "b=6"
"z=3" "c=7"
) do set "%%~a"
echo %x% %y% %z% %a% %b% %c%
Mind that the quotes around the items are mandatory because = is a delimiter. You can put everything on line and to use as separators , ,; ,<space>
May be with a lot of variables this can save you from some writing...?
this can be rewritten like this:
#echo off
set "vars=x=1,y=2,z=3,a=5,b=6,c=7"
for %%a in ("%vars:,=","%") do set "%%~a"
echo %x% %y% %z% %a% %b% %c%
And thus you'll need to change only the vars value.

How to split string with "=" without for loop in batch file

I would like split a string in two part with = as delimiter.
I saw this post but I do not manage ta adapt.
I try this:
set "str=4567=abcde"
echo %str%
set "var1=%str:^=="^&REM #%
echo var1=%var1%
Why it does not work?
While not a bulletproof solution (use the for, artoon), without more info, this can do the work
#echo off
setlocal enableextensions enabledelayedexpansion
set "str=4567=abcde"
rem Step 1 - remove the left part
set "str1=!str:%str%!"
rem Step 2 - Get the right part
set "right=!str:*%str1%!"
rem Step 3 - Get the left part
set "left=!str:%right%=!"
set "left=%left:~0,-1%"
echo [%left%] [%right%]
edited to adapt to comments (OP code in comments adapted to my code, or the reverse)
for /f "delims=" %%i in ('set') do (
setlocal enabledelayedexpansion
rem Step 1 - remove the left part
set "str=%%i"
for %%x in ("!str!") do set "str1=!str:%%~x!"
rem Step 2 - Get the right part
for %%x in ("!str1!") do set "right=!str:*%%~x!"
rem Step 3 - Get the left part
for %%x in ("!right!") do set "left=!str:%%~x=!"
set "left=!left:~0,-1!"
echo [!left!] [!right!]
endlocal
)
And no, as previously indicated this is not bulletproof and some of the variables show problems (had I said it is not bulletproof?).
What i don't understand is the requirement to not use a for loop and then use a for loop. It is a lot easier this way
for /f "tokens=1,* delims==" %%a in ('set') do (
echo [%%a] [%%b]
)
Another alternative (not as easy as the for, more stable than the previous one, non bulletproof) is
for /f %%a in ('set') do (
call :split left right %%a
echo [!left!] [!right!]
)
goto :eof
:split leftVar rightVar data
set "%~1=%~3"
setlocal enabledelayedexpansion
set "data=%*"
set "data=!data:*%1 %2 %3=!"
set "data=%data:~1%"
endlocal & set "%~2=%data%"
goto :eof
As npocmaka commented above, = has special meaning and cannot be replaced with traditional variable string manipulation. If you know the length of either side of the equal sign, you could strip off a number of characters. For example, if "4567" will always be 4 characters, you could set "var1=%str:~0,4%". Or if "abcde" will always be 5 characters, you could set "var1=%str:~0,-6%" (5 chars + 1 for the equal sign).
Otherwise, a for loop is your only other option without using 3rd party utilities.
for /f "delims==" %%I in ("%str%") do set "var1=%%I"
If you've got grep installed, you can do something like:
echo %str% | grep -P -o "^[^=]*"
... but you'd still need to capture its output with another for /f loop.
If you are allergic to for loops, and as an exercise in providing a solution to your question without any regard for efficiency, here's how you get the first half of your string without using a single for loop. Put grep and its dependencies in your %PATH%. Then:
echo %str% | grep -P -o "^[^=]*" >temp.txt
set /P "var1="<temp.txt
del temp.txt
echo %var1%
There, I fixed it!

Trying to reformat a very large csv with a batch file

I have an application that exports data in the format:
1a,1b,1c1,1c2,1c3, ... (up to 1c100),1d1,1d2,1d3, ... (up to 1d100)
2a,2b,2c1,2c2,2c3, ... (up to 2c100),2d1,2d2,2d3, ... (up to 2d100)
etc.
and I am trying to reformat this into
1a,1b,1c1,1d1
1a,1b,1c2,1d2
.
.
1a,1b,1c100,1d100
2a,2b,2c1,2d1
2a,2b,2c2,2d2
etc.
I figured that if this can be done a row at a time I can just loop through the file. However I can't find a way of doing a single row with either tokens, a list, or even as a string function. There is too much data to process in a single operation (each value is about 12 chars). Tokens limit at (roughly) 64/202, a list at about 107/202 and a string at about 1000/2300
Does anyone know how this can be written into a new file?
I was trying things like:
#echo off
setlocal enableDelayedExpansion
set dimCnt=0
<example.csv (
set /p "dimList=" >nul
for %%D in (!dimList!) do (
set /a dimCnt+=1
set "dim[!dimCnt!]=%%D"
)
)
echo
for /l %%I in (3 1 102) do echo !dim[1]!,!dim[2]!,!dim[%%I]!
</code>
..besides the fact that I have missed out the last variable in the line (need to add 100 to it), I can't get more than about 80-110 values out of the list (I guess it depends on value string length)
#echo off
setlocal enableextensions enabledelayedexpansion
(for /f "tokens=1,2,* delims=," %%a in (example.csv) do (
set "data=%%c"
set "i=0"
for %%f in ("!data:,=" "!") do (
set /a "i+=1"
set "d[!i!]=%%~f"
)
set /a "end=!i!/2"
set /a "j=!end!+1"
for /l %%i in (1 1 !end!) do (
for %%j in (!j!) do echo %%a,%%b,!d[%%i]!,!d[%%j]!
set /a "j+=1"
)
)) > output.csv
endlocal
This iterates over the file, getting the first two tokens in the line (%%a and %%b), the rest of the line (%%c) is splitted and each value stored in an environment variable array (kind of). Then, the array is iterated from the start and from the middle, reading the needed values to append to %%a and %%b and generating output file.
#ECHO OFF
SETLOCAL
(
FOR /f "tokens=1,2,*delims=," %%a IN (u:\long.csv) DO (
SET rpta=%%a
SET rptb=%%b
CALL :rptcd %%c
)
)>newfile.txt
GOTO :EOF
:rptcd
SET /a lines=100
SET lined=%*
FOR /l %%x IN (1,1,99) DO CALL SET lined=%%lined:*,=%%
:loop
IF %lines%==0 GOTO :EOF
SET /a lines-=1
CALL SET lined=%lined:*,=%
FOR /f "delims=," %%x IN ("%lined%") DO ECHO %rpta%,%rptb%,%1,%%x&shift&GOTO loop
GOTO :eof
This should get you going - just need to change the input filename and output filename...
Your code does not work because SET /P cannot read more than 1023 bytes. At that point it returns the data read so far, and the next SET /P picks up where it left off. Adapting your code to compensate will be very difficult. You would be better off using FOR /F as in MC ND's answer. But beware, batch has a hard limit of 8191 characters per line in pretty much all contexts.
Better yet, you could use another scripting language like JScript, VBS, or PowerShell. Performance will be much better, and the code much more robust and far less arcane. I love working with batch, but it simply is not a good text processing language.

Resources