DOS - Trying to understand delimiters to find lowest line count inf files - for-loop

I found a bug in a bat file I was writing and by accident found a fix however I don't understand why the fix worked.. Which is ultimately my question, or if there are other problems with this logic that I have not discovered.
The script does a line count in 2 or more files then the goal is to find the file with the lowest line count and later use that count in a variable later in the script as a parameter.
Example file 1 (9 lines):
Wrote,0
Wrote,1
Wrote,2
Wrote,3
Wrote,4
Wrote,5
Wrote,6
Wrote,7
Wrote,8
Example file 2 (10 lines):
Wrote,0
Wrote,1
Wrote,2
Wrote,3
Wrote,4
Wrote,5
Wrote,6
Wrote,7
Wrote,8
Wrote,9
The main difference that I see is that lines 9 & 10 in file 2 are two digit line numbers
NOTE: I am not referring to the actual value on each line but only the line number's count value as my code does not try to parse the lines but only get the line count.
Near the top of the code I create then append the count values for each file into a temp file called ~numbers.txt so to simulate that process here assume my starting code is:
echo 9 > ~numbers.txt
echo 10 >> ~numbers.txt
type ~numbers.txt
So now I have a file with two lines "9" and "10" and of course 9 is lower than 10 but it also is one digit less than 10.
Now I attempt to find the lowest number contained in the file ~numbers.txt by reading the 1st line into a variable then looping and evaluating if current value is less than prior value.
setlocal enabledelayedexpansion
set /p lowest=<~numbers.txt
FOR /F "delims=" %%G in (~numbers.txt) DO IF %%G LSS !lowest! SET lowest=%%G
echo lowest num = %lowest%
When I run this code I get this output which is wrong:
lowest num = 10
What I have found is that if I remove the "delims=" from the FOR loop I will get the correct result of 9.
ALSO and what really screws up my understanding of the problem; If I only change the values in ~numbers.txt to 10 and 100 and leave the FOR loop line alone (keeping "delims=" ) then I get the correct value of 10 as my answer???
This is bad because it implies that my logic is flawed and that other number combinations will also yield different results.
Can anyone explain to me why this is the case and/or give an example on how to get the correct lowest number regardless of the values?
Thanks...

If you don't see something it doesn't mean it doesn't exist. (I'm talking of spaces here)
Try this instead:
> ~numbers.txt echo 9<press ENTER immediately>
>> ~numbers.txt echo 10<press ENTER immediately>
This will be truly WYSIWYG, no spaces at the end of line, no text comparisons by if because strings are not all numbers.
Of course, stripping the spaces with default delims (one of which is a space) also works fine as you have noticed already.
Consider this way of determining lowest line count:
set /a minlines=0x7FFFFFFF
for /f "tokens=2 delims=:" %%a in ('find /c /v "" *.txt') do set /a minlines="%%a-(minlines-%%a)*(minlines-%%a>>255)"
echo %minlines%
Note: no ifs, no delayed expansion

Related

How can I pad numbers with leading zeros, to a fixed length?

I've tried so sort this for the better part of the morning.
It is actually the same question as this one from 2013, to which no one replied:
batch script with FOR does not work
I'll do my best to format the code so that it is easy to follow and maybe I'll get an answer...
I am doing an archive project from our help desk ticket system.
For sorting purposes, the files will have the ticket number and the date.
However, the ticket number varies in length. To fix this, all ticket numbers are to be 6 digits, with the shorter numbers padded with preceding zeroes (i.e. 1234 becomes 001234).
I can get the ticket number, but I need to find its length to know how many zeroes to add.
This works:
echo off
set my_str=12345
set length=0
:Loop
if defined my_str (
set my_str=%my_str:~1%
set /A "length+=1"
goto Loop)
echo the string is %length% characters long
However, I get a bunch of ticket numbers in a list.
So, I read through this:
set statements don't appear to work in my batch file
And got lost reading this:
http://www.computing.net/howtos/show/batch-script-variable-expansion-win200-and-up/248.html
And I tried this:
setlocal enabledelayedexpansion
echo off
for /f %%a IN (test.txt) do (
set my_str=%%a
set length=0
:Loop
if defined my_str (
set my_str=!my_str:~1!
set /A "length+=1"
echo %length%
goto Loop)
echo the string is %length% characters long
)
But it only reads the FIRST line of test.txt
Why does the FOR /F loop fail?
How do I get it to work?
You do not need to know the length of the ticket number string (when you can assure it won't be longer than 6 characters), you can prefix 5 zeros and split off the last 6 characters then:
set my_str=12345
set my_str=00000%my_str%
set my_str=%my_str:~-6%
echo %my_str%
If you still want to get the string length, consult this post.
You cannot use goto within the for body as goto breaks the for loop context. However, you can call a sub-routine within for, which in turn may contain goto.
Your batch script does exactly what you are describing: reading the first line of a number of files (in your case, only the one file) and determining the length of the first line. I.e. you got the inner part of your for loop wrong.
I don't know the correct solution either but I think that this page may be of some help to you:
http://ss64.com/nt/for_f.html
I hope that helps.
This seems like a XY problem. Instead of worrying about calculating the length of a string, just use the built-in substring methods to create a padded string.
#echo off
setlocal enabledelayedexpansion
for /f %%a IN (test.txt) do (
set my_str=000000%%a
#echo !my_str:~-6!
)
The end result echo'ed will be a six-digit number left-padded with zeroes.

remove first characters of multiple file names with a bat file - what is ~%X%,?

I need to create a script, that will remove first six characters from a huge amount of files (with different names). I tried this example from another question, but I want to understand it better:
#echo off
setlocal enabledelayedexpansion
set X=3
for %%f in (*) do if %%f neq %~nx0 (
set "filename=%%~nf"
set "filename=!filename:~%X%,-%X%!"
ren "%%f" "!filename!%%~xf"
)
popd
I can see, that modifying the X in -%X%! I actually trim the X number of first characters from all the files in the folder. I don't know what the ~%X%, is - I can only see that if it is not a number higher than 0, the script won't run. I also don't know what set X=3 is - I can only see that there is no difference whether it is present in the bat file or not. Could anyone please explain to me the syntax of this file?
Thanks in advance!
That method is called Substring.
You can see a lot of examples and explanation here: http://ss64.com/nt/syntax-substring.html
The first number is the start index, and the second number is the last index of.
Example:
#echo off
Set "Filename=TestFile.txt"
Set "Filename=%Filename:~0,-4%"
Echo %FILENAME%
pause
In that code we start reading from index "0" (the first letter of the string), and stop reading at "-4", then we substract from 0 to -4 so the result is: "TestFile"
I hope this helps.

Windows: copy a file until the file not exist

I want to use a Windows batch file in to copy a file (myfile0001.bdg) from one specific directory to another. But I want to check if the file in the target directory exists and if the answer is yes, increment the file with 0001 and check again if the file exists (myfile0002.bdg) and so on, until the file does not exist, and copy the file with the new title.
So, if in the target directory, I have these files:
myfile0001.bdg
myfile0002.bdg
myfile0003.bdg
myfile0004.bdg
myfile0005.bdg
myfile0006.bdg
The new file should be named myfile0007.bdg. The next time I will execute the batch, the new file will be myfile0008.bdg, etc.
I know there is a command "IF EXIST" but I don't know to do what I need.
==============
I'm under Windows 7 x32
The source directory is "C:\USERS\RAMBYTES\DOCUMENTS\"
The target directory is "P:\BACKUP\"
The file is "MYFILE0001.BDG"
Something like this:
#echo off
set source_file=C:\USERS\RAMBYTES\DOCUMENTS\MYFILE0001.BDG
set target_dir=P:\BACKUP\
set done=0
for /l %%i in (1,1,1000) do (
call :check_and_copy %%i
if errorlevel 1 goto :eof
)
goto :eof
:check_and_copy
setlocal EnableDelayedExpansion
set num=000000%1
set fnum=!num:~-4!
set fname=%target_dir%\myfile%fnum%.bdg
rem echo %fname%
if not exist "%fname%" (
echo copying %source_file% to %fname%
exit /b 1
)
exit /b 0
There is no error handling in case there are more than a 1000 files present in the target directory. If you want to increas the file limit, you need to adjust the "main" for loop and the "formatting" of the number in the sub-program
The trick with adding the leading zeros was taken from here: https://stackoverflow.com/a/9430912/330315
#ECHO OFF
SET destdir=c:\destdir
SET newname=myfile0000
FOR /f %%i IN (' dir /b /on %destdir%\myfile????.bdg ' ) DO SET newname=%%~ni
SET newname=1%newname:~-4%
SET /a newname+=1
SET newname=myfile%newname:~-4%.bdg
COPY myfile0001.bdg %destdir%\%newname%
change the destination directory as desired, and include the source directory if required.
Take the file name.
Extract the numeric part.
Check if the corresponding target name exists.
If so,
4.1) increase the numeric part;
4.2) if it doesn't exceed the highest possible number go to Step 3;
4.3) otherwise terminate.
If the target name doesn't exist, copy the file with the current numeric part and terminate.
Although algorithmically the condition in 4.2 may be more natural to be checked just after increasing the numeric part, like I put it above, the below script performs the check at a different point, at the beginning of the loop, which starts just after extracting the original numeric value from the source filename. Implementationally, that seemed to me more convenient.
In all other respects, the script implements the same algorithm:
#ECHO OFF
SET "fname=%~n1"
SET counter=1%fname:~-4%
:loop
IF %counter% GTR 19999 (
1>&2 ECHO Cannot copy the file: no free slots.
EXIT /B 1
)
SET "targetname=%~2\%fname:~0,-4%%counter:~1%%~x1"
IF EXIST "%targetname%" (
SET /A counter+=1
GOTO loop
) ELSE (
COPY %1 "%targetname%"
)
To explain some parts:
The tilde (~) in references to positional parameters means de-quoting of the correspondent parameter.
Sometimes in the script, the tilde is also directly followed by a modifier. Two modifiers are used here, n and x. The former causes the parameter to expand to the corresponding file name only (without the path and the extension) and the latter extract only the extension.
You can learn more about modifier in the built-in of the FOR command (by running
The fname environment variable is needed because extracting of name parts can only be done on environment variables. The %fname:~-4 expression, in particular, evaluates to the last four characters of the fname value. More specifically, it reads: extract the substring that starts at the 4th character from the end and, as -4 isn't followed by another argument, includes all the characters from that point till the end of the string.
Another similar-looking expression, %fname:~0,-4%, does the opposite: it returns the contents of fname except the last four characters. The meaning of the numbers is this: extract the substring that starts at the beginning of the string (offset 0) and spans the range up to and including the character at the offset of 4 from the end.
One more expression of this kind, %counter:~1, extracts the characters starting from the second one (i.e. offset 1) and up to the end of string (no second argument).
Run SET /? to find out more about string expressions.
The counter implementation may also require explanation. The 1 added in front of the numeric part of the file name is needed so that the entire value could be interpreted and processed correctly when incrementing it.
The thing is, a numeric value starting with a 0 is treated as an octal by the CMD command processor, so, putting 1 at the beginning makes it to interpret the number as a decimal, which it actually is. When constructing the complete name of the target file, we simply need to discard the added 1, which is what the %counter:~1 is used for.

CMD return specific lines from piped input

Bored in class trying to figure this out.
On windows command prompt:
ipconfig /all return all the loopback, tunnel, etc.
if I run ipconfig /all | find /n "Internal" it will return [11]Ethernet Adapter Internal. What I want to do is substring the 11 off the beginning and then pipe this to something else which will allow me to return lines 11-19 or whatever. Can you do this in a single line similar to jquery and chaining?
It is simple to parse out the 11 using a FOR /F loop. But making use of that value is difficult in a one liner from the command line.
Here is the best I could come up with for a one liner to run from the command line.
set "stop="&for /f "delims=[]" %N in ('ipconfig /all^|find /n "Internal"') do #for /f "tokens=1* delims=:" %A in ('ipconfig /all^|findstr /n "^"') do #if %A geq %N if not defined stop echo(%B&>nul 2>nul set /a 1/(%A-%N-8)||set stop=1
The shortest one liner I could think of uses a different strategy.
set n=&for /f "delims=" %A in ('ipconfig/all') do #if defined n (set/a"1/n,n-=1">nul 2>&1&&echo %A) else echo %A|find "Internal"&&set n=8
But the above solution ignores blank lines.
If you want to preserve blank lines then the following works:
set n=&for /f "tokens=1* delims=:" %A in ('ipconfig/all^|findstr/n "^"') do #if defined n (set/a"1/n,n-=1">nul 2>&1&&echo(%B) else echo %B|find "Internal"&&set n=8
EDIT
I shortened and simplified the logic of the 2nd and 3rd solutions a bit.
I'll try to explain the 2nd option:
I first explicitly undefine a counter and then use FOR to read the output of the IPCONFIG/ALL commmand. Now begins the fun part within the DO clause.
At first the counter is undefined so the ELSE clause is executed. I pipe the current line to FIND looking for "Internal". If the string is not found then nothing happens and we progress to the next line. But if it is found then the line is printed. Then the code after && is executed since the string was found. There I set the counter to 8 because we want the next 8 lines printed.
The counter is defined for the remainder of the lines, so we now switch to the first part of the IF statement. I use SET /A to do some math. First I divide 1 by the current value of the counter. IF the counter is 0 then an error is raised and the rest of the statement is ignored. If the counter is not 0 then I next decrement the counter by 1. I redirect both stdout and stderr to nul because we don't want to see any messages from the SET /A command. If the SET command was successful, then I echo the line. Since I initially set the counter to 8 when the string was found, up to 8 lines will be printed before the counter reaches 0.

windows command for grand total

Is there a easy way to calculate the grand total of all the numberic values specified line by line using a windows command (or batch file - least prefer)
suppose
7612
7724
19844
20092
20184
20468
27100
36456
39428
54264
69008
97208
assume this is in a file
I want the total of all the values. thanks in advance
I'm not aware of any command line utility to compute such a sum. But you can use a for loop iterating through the file. Something like this will work but you will need a helper batch file. In Sum.bat dump:
REM. Turn off echo-ing of individual commands
#echo off
REM. Set variable a to 0. /a mean arithmetic expression
set /a sum = 0
REM. For loop updating the sum as we go
FOR /F %%i IN (file.txt) DO set /a sum += %%i
REM. Output
echo %sum%

Resources