Save values batch file - batch-file

I wrote this script to looking for volume with enough free space:
#echo on
setlocal
set gbsize=1,073,741,824
Set gbsize=%gbsize:,=%
for %%A in (A B C D) do (
for /f "tokens=3,4,5,*" %%B in ('dir %%A:\') do (
set bytesfree=%%B
set bytesfree=%bytesfree:,=%
if %%D == free If %bytesfree% gtr %gbsize% echo hi
)
)
My problem is that the bytesfree variable dosent save its value. the output is(echo is on)
C:\Users\Desktop>(
set bytesfree=**780,607,488**
set bytesfree=**23167987712**
if free == free If 23167987712 GTR 1073741824 echo hi
)
hi
looks like the bytesfree losed its value.
Can anyone please help? and provide some explantation?
thanks.

To expand on Joey's (original short) answer, the entire for expression is parsed at once, and the % expansion occurs at parse time. But the value you want isn't there until the DO clause has executed. That is why you need delayed expansion. Read the help on for by typing HELP FOR from the command line.
Based on the comment from your previous question https://stackoverflow.com/a/9096601/1012053, it looks like you are attempting to find the drive with the most free space > 1GB.
Your current code has a slight risk of including the wrong line from the DIR command. I've modified it to filter the output using FINDSTR with a regular expression.
EDIT - Also, the IF command cannot properly compare numbers that exceed 2147483647.
>if 2147483647 gtr 2147483646 echo greater
greater
>if 2147483648 gtr 2147483647 echo greater
>if 1000000000000 gtr 2147483647 echo greater
>if 2147483648 equ 2147483647 echo equal
equal
>if 1000000000000 equ 2147483647 echo equal
equal
So the numbers need to be 0 prefixed and the IF command must be forced to do a string comparison instead of a numeric comparison. I forced a string comparison by enclosing the 0 prefixed number in quotes.
#echo off
setlocal enableDelayedExpansion
set "gbsize=1,073,741,824"
set "gbsize=%gbsize:,=%"
set "maxfree=000000000000000"
set "maxdrive="
for %%A in (C D) do (
for /f "tokens=3" %%B in ('dir %%A:\^|findstr /r /c:"^ *.* *Dir(s).*bytes free$"') do (
set "bytesfree=000000000000000%%B"
set "bytesfree=!bytesfree:,=!"
set "bytesfree=!bytesfree:~-15!"
if "!bytesfree!" gtr "!maxfree!" (
set "maxfree=!bytesfree!"
set "maxdrive=%%A:"
)
)
)
for /f "tokens=* delims=0" %%A in ("%maxfree%") do set maxfree=%%A
echo Drive with max free space is %maxdrive% %maxfree% bytes free
if %maxfree% gtr %gbsize% echo That is more than 1GB
An alternate method using WMIC
#echo off
setlocal enableDelayedExpansion
set "gbsize=1,073,741,824"
set "gbsize=%gbsize:,=%"
set "maxfree=000000000000000"
set "maxdrive="
for /f "skip=1 tokens=1,2" %%A in ('wmic volume get DriveLetter^, FreeSpace') do (
if "%%B" neq "" (
set "bytesfree=000000000000000%%B"
set "bytesfree=!bytesfree:~-15!"
if "!bytesfree!" gtr "!maxfree!" (
set "maxfree=!bytesfree!"
set "maxdrive=%%A"
)
)
)
for /f "tokens=* delims=0" %%A in ("%maxfree%") do set maxfree=%%A
echo Drive with max free space is %maxdrive% %maxfree% bytes free
if %maxfree% gtr %gbsize% echo That is more than 1GB

Short answer:
Use
setlocal enabledelayedexpansion
instead of just setlocal and then use !bytesfree! to refer to the variable (just replace % by !).
Longer answer:
This is because cmd expands variables as it parses a command, not when the command is run (which, obviously happens after parsing). A command in this case could also be a complete for statement including the block after it. So all instances of %bytesfree% within the loop are replaced by their value before the loop, which happens to be an empty string.
Delayed expansion is a special form of variable expansion which expands variables when a command is run instead of when parsing it. It can be enabled with
setlocal enabledelayedexpansion
(within a batch file) or
cmd /v:on
(for a complete session, but not in a batch file, unless you don't want it to resume).
Then the syntax !bytesfree! is used to refer to variables which are then expanded just prior to running a command. So when setting and using the same variable within a block (for loop, if, etc.) you pretty much always want to use delayed expansion.

Related

Increment a variable already incremented in another loop

I would like to automate the execution of several commands to retrieve files from pods, with oc client.
I succeeded in incrementing the name of my variables, but I can't increment them in another loop to get their content.
#echo off
set /p "nbPod=How many times should RSYNC be used?? "
set num=0
for /l %%p in (1, 1, %nbPod%) do (
set /a "num = num + 1"
setlocal EnableDelayedExpansion
set /p "podName!num!= Enter pod name for pod number %%p ? "
)
set /a iteration=1
:loop
if %iteration% leq %num% (
setlocal EnableDelayedExpansion
echo %podName!iteration!%
::oc rsync %podName!iteration!%:/my_command_0
::oc rsync %podName!iteration!%:/my_command_1 [etc]
set /a "iteration = iteration + 1"
goto :loop
)
NB : I don't know why but set /a num+=1 wasn't working.
#ECHO OFF
SETLOCAL EnableDelayedExpansion
:: remove variables starting podname
FOR /F "delims==" %%b In ('set podname 2^>Nul') DO SET "%%b="
set /p "nbPod=How many times should RSYNC be used?? "
for /l %%p in (1, 1, %nbPod%) do set /p "podName%%p= Enter pod name for pod number %%p ? "
for /l %%p in (1, 1, %nbPod%) do (
echo !podName%%p!
rem within a block, use REM, not :: as :: is a broken label which terminates the block
rem oc rsync !podName%%p!:/my_command_0
rem oc rsync !podName%%p!:/my_command_1 [etc]
)
GOTO :EOF
Always verify against a test directory before applying to real data.
A few little issues with your code.
setlocal makes a copy of the current environment and processing continues using that new environment. An endlocal statement disposes of the new environment and restores the original. Reaching physical end-of-file is an implicit endlocal.
Consequently, it's normal to follow the initial #echo off with a setlocal as any statements executed before the setlocal are retained in the environment after the batch ends, which can cause confusion for subsequent batches as the variables use may be unexpectedly set.
There is a limit to the number of nested open setlocals that can be used (about 300). Unlikely to be a problem in this case, but something to note.
nbpod is your limit, so there's no requirement to use num or iteration.
I've no idea what the comments in the final loop are, but they MUST be rem statements within a block (parenthesised sequence of commands). OK- probably they are commented-out what I'm actually doing lines....
The for /f ... %%b command will process the list generated by set command, which would be something like
podname1=one
podname2=two
The delims= option causes for/f to assign the (default) first "token" in the line (podname1,podname2) to %%b and "sets" the value to nothing, deleting any stray variables from the environment. The 2^>nul suppresses error messages if there are no current variables named podname....
Then input the names as podname1... according to %%p.
And read them back using delayedexpansion, the current value of [podname strung with %%p]

Avoid a null value in last for loop iteration?

I have a text file with one string per line (only a couple lines total). This file needs to be searched and each string stored. The script will ultimately prompt the user to choose one of the stored strings (if more than one string/line is present in the file), but the for loop is iterating an extra time when I don't want it to.
I'm using a counter to check the number of iterations, and I also read that it's probably a NL CR regex issue, but the finstr /v /r /c:"^$" in the for file-set like in this post Batch file for loop appears to be running one extra time/iteration doesn't work for me (but I probably don't understand it correctly).
The "pref" term is because the strings are to be eventually used as a prefix of files in the same folder...
#echo off
setlocal enabledelayedexpansion
set /a x=1
for /f %%a in (sys.txt) do (
set pref!x!=%%a && echo %^%pref!X!%^% && set /a x+=1
)
echo last value of x = !x!
for /L %%a in (1,1,!x!) do (
echo !pref%%a!
)
REM The rest would be to prompt user to choose one (if multiple) and
REM then use choice as a prefix with a ren %%a %prefX%%%a
If the "sys.txt" contains three lines with strings A, B, C respectively, then the output I currently get is:
pref1
pref2
pref3
last value of x = 4
A
B
C
ECHO is off.
ECHO is off. is not desired, clearly.
You just need to change your increment structure like this. (set it before each line starting from a base of 0)
#Echo Off
Setlocal EnableDelayedExpansion
Set "i=0"
For /F "UseBackQ Delims=" %%A In ("sys.txt") Do (
Set/A "i+=1"
Set "pref!i!=%%A"
Echo(pref!i!)
Echo(last value of i = %i%
For /L %%A in (1,1,%i%) Do Echo(!pref%%A!

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!

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