Batch file decides that 4 > 39, How do I fix it? - batch-file

Note: The file Default.txt contains one line with these three characters:1:X
#echo off
set r=0
:: For loop retrieves lines of text from the file Default.txt
FOR /F "tokens=1,2 delims=:" %%a IN (Default.txt) DO (
:: Each line is saved to a different variable.
set _%%a=%%b
set n=%%a
)
set ln=1
setlocal enabledelayedexpansion
:process
:: This loop processes all the lines in the text file.
set r=0
if "%n%" GTR "0" (
:len
:: This loop determines the length of each string.
if not "!_%ln%:~%r%,1!"=="" (
set /a r=%r%+1
goto len
)
:space
:: This loop adds spaces to each string so they will all be 39 characters in length.
if "%r%" LEQ "39" (
:: Note that there is a mandatory space at the end of the following line.
set _%ln%=!_%ln%!
set /a r=%r%+1
goto space
)
set /a n-=1
set /a ln+=1
goto process
) else (
endlocal
set _1=%_1%
)
echo %_1%]
pause >nul
When the script is run however, instead of adding 38 spaces, it only adds 3.
By turning echo back on, I found the exact point where it exits the :space loop.
C:\>if "1" LEQ "39" (
set _1=!_1!
set /a r=1+1
goto space
)
C:\>if "2" LEQ "39" (
set _1=!_1!
set /a r=2+1
goto space
)
C:\>if "3" LEQ "39" (
set _1=!_1!
set /a r=3+1
goto space
)
Up to this point, everything is working as it should.
Suddenly:
C:\>if "4" LEQ "39" (
set _1=!_1!
set /a r=4+1
goto space
)
For some reason, 4 is suddenly greater than 39, and it moves on to the next section instead of incrementing the variable and looping again like it should.
C:\>set /a n-=1
C:\>set /a ln+=1
C:\>goto process
And the program moves on and only 3 spaces are ever added to the variable.
I have no idea what the problem is and would be grateful for any insight.

C:\>if 4 LEQ 39 (
set _1=!_1!
set /a r=4+1
goto space
)
Remove quotations and try that should work , quotes are normally used with strings.

When comparing numerics, don't enclose them in quotation marks.
command: if "4" leq "39" echo hi
output: (empty line)
command: if 4 leq 39 echo hi
output: hi
The reason for that is that "4" is alphabetically after "39", so "4 is greater than "3. When comparing using quotation marks, the comparison is alphabetic, not numeric as you intended.
You've got a few other problems with your script. Don't put labels within parenthetical code blocks. You need to find some other place to put :len and :space outside of the if statement where they currently live. Strictly speaking, :: is also label named :, not a substitute for rem. When using :: as a comment, avoid using it within parenthetical code blocks as well. Use rem instead. Also, indent your code to make it easier to ensure you've got the same number of ( as ). Let me ask you: which is more readable?
option 1:
...
:space
:: This loop adds spaces to each string so they will all be 39 characters in length.
if "%r%" LEQ "39" (
:: Note that there is a mandatory space at the end of the following line.
set _%ln%=!_%ln%!
set /a r=%r%+1
goto space
)
set /a n-=1
set /a ln+=1
goto process
) else (
endlocal
set _1=%_1%
)
echo %_1%]
pause >nul
The else is a continuation of the if statement above it, right? Wrong! Properly indented, you'd see that it's part of an if statement much higher in the script.
option 2:
rem This loop adds spaces to each string so they will all be 39 characters in length.
if "%r%" LEQ "39" (
rem Note that there is a mandatory space at the end of the following line.
set _%ln%=!_%ln%!
set /a r=%r%+1
goto space
)
set /a n-=1
set /a ln+=1
goto process
) else (
endlocal
set _1=%_1%
)
echo %_1%]
pause >nul
I'll never understand why some people insist on left-justifying every line of code they write. It only makes things much more difficult to troubleshoot.
You know, there are more efficient ways to repeat characters. Rather than looping, you could do variable substring extraction.
set "spaces= "
set "39spaces=%spaces:~-39%"
If you want to get the length of a string, the fastest way I've found to do that is based on jeb's answer here:
:length <return_var> <string>
setlocal enabledelayedexpansion
if "%~2"=="" (set ret=0) else set ret=1
set "tmpstr=%~2"
for %%I in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if not "!tmpstr:~%%I,1!"=="" (
set /a ret += %%I
set "tmpstr=!tmpstr:~%%I!"
)
)
endlocal & set "%~1=%ret%"
goto :EOF
Example usage:
command: call :length len "The quick brown fox"
command: echo %len%
output: 19
Even if you're getting the length of a 2000 character line, that loop still counts the length in only 13 iterations, rather than potentially thousands.

Prudviraj's answer answers why your original code doesn't work, but for an easier way of padding a string to 39 characters, you could try:
Make39.bat
#echo off
setlocal
set "R=%1"
set "R=%R% " REM append 39 spaces
set "R=%R:~,39%" REM take first 39 characters
echo :123456789012345678901234567890123456789:
echo :%R%:
which works as follows:
S:\>make39 abc
:123456789012345678901234567890123456789:
:abc :
S:\>make39 "Quite a long string"
:123456789012345678901234567890123456789:
:Quite a long string :
Your comments seem to imply no string will initially be longer than 39 characters; you would have to get more inventive if this were possible (I would probably take the first 39 characters of the original R and see if that and R differ: if they did, the original would have been longer, so there would be no need to add padding).

Related

Batch file: Where is the phantom line break coming from in this code?

I am trying to append a backslash to the end of a string (a folder name), and instead of getting a backslash, I seem to be getting a line break, 13 spaces, then finally the backslash. Where is this coming from?
Here is the code I wrote:
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:: After the user chooses a folder from a menu, the choice is stored in ChosenFolder
:: A typical value for ChosenFolder would be "3 .\Folder1"
set "ParentFolder=!ChosenFolder:~2!^\"
echo ParentFolder is !ParentFolder!
The output I get is:
ParentFolder is .\Folder1
\
If I just manually set the ChosenFolder variable to "3 .\Folder", this error doesn't happen, so here is the code that ultimately generates the faulty value of ChosenFolder (a slightly modified version of Magoo's code that answered this question, bless his heart!)
SET "choicenames=z0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy"
:: remove variables starting #
FOR /F "delims==" %%e In ('set # 2^>Nul') DO SET "%%e="
FOR /L %%e IN (1,1,%pagewidth%) DO SET "#spaces=!#spaces! "
:: Read dirnames to #nn, count to #entries
FOR /d %%e IN ("%~1\*.") DO SET /a #entries+=1&SET "#!#entries!=%%e"
SET /a #entries+=1&SET "#!#entries!=z Quit."
SET /a #columns=(#entries + pagesize - 1) / pagesize
SET /a #rows=(#entries + #columns - 1)/#columns
SET /a #columnwidth=(pagewidth/#columns) - 3
SET "#choices=z"
FOR /L %%e IN (1,1,%#entries%) DO (
rem column contents - max length + terminal spaces
IF %%e neq %#entries% (
SET "#%%e=!#%%e:~-%#columnwidth%!%#spaces%"
SET "#%%e=!choicenames:~%%e,1! !#%%e:~0,%#columnwidth%!"
SET "#choices=!#choices!!choicenames:~%%e,1!"
)
)
FOR /L %%e IN (1,1,%#rows%) DO (
SET /a cols=%%e + %#rows%
SET /a #line=%%e + (%#rows% * 2^)
SET "cols=!cols! !#line!"
SET "#line=!#%%e!"
FOR %%y IN (!cols!) DO IF DEFINED #%%y SET "#line=!#line! !#%%y!"
ECHO !#line!
)
IF %#entries% gtr 36 (
choice /cs /c %#choices%
) ELSE (
choice /c %#choices%
)
IF ERRORLEVEL 2 (
ECHO ERRORLEVEL is %ERRORLEVEL%
SET /a #choices=%ERRORLEVEL%-1
CALL SET "ChosenFolder=%%#!#choices!%%"
ECHO choice made : !ChosenFolder:~2!
CHOICE /C snc /N /M "(S)elect, (N)avigate or (C)ancel? "
IF errorlevel 3 goto begin
IF errorlevel 2 CALL :SelectFolder !ChosenFolder:~2!
IF errorlevel 1 EXIT /B 0
) ELSE GOTO begin
#? contains the name as, eg : 3 u:\Folder1 . The spaces are added to lay out the menu appropriately, so they would need to be deleted (hence the series of spaces when manipulated and the need to lop off the leading 2 characters).
\ does not need to be escaped if simply appended to the string.
Note however - Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign " or a terminal backslash or Space. Build pathnames from the elements - counterintuitively, it is likely to make the process easier. If the syntax set var="value" is used, then the quotes become part of the value assigned.
FOR /d %%e IN ("%~1\*.") DO SET /a #entries+=1&SET "#!#entries!=%%e"&SET "#originalnames!#entries!=%%e"
To establish a parallel list of names, #originalnames?.
IF ERRORLEVEL 2 (
ECHO ERRORLEVEL is %ERRORLEVEL%
SET /a #choices=%ERRORLEVEL%-1
CALL SET "ChosenFolder=%%#originalnames!#choices!%%"
ECHO choice made : !ChosenFolder!
CHOICE /C snc /N /M "(S)elect, (N)avigate or (C)ancel? "
IF errorlevel 3 goto begin
IF errorlevel 2 CALL :SelectFolder
rem IF errorlevel 1 EXIT /B 0
) ELSE GOTO begin
GOTO :EOF
:selectfolder
ECHO +%chosenfolder%+
FOR /f "delims=" %%e in ("%chosenfolder%") do SET "Parentfolder=%%~dpe"
echo ParentFolder of %chosenfolder% is +%ParentFolder%+
GOTO :eof
(Note + on either end of the names to show absence of spaces.)
This assigns the original name (not the displayed name which may have been truncated) to chosenfolder.
Since chosenfolder is not within (...) (ie. a code block) you can use %chosenfolder% to access its contents. !chosenfolder! will also work, but it implies that the value of chosenfolder may vary because it's within a code block - but within the subroutine :SelectFolder there are no code blocks.
chosenfolder is of the form u:\folder 1. so the for assigns this to %%e and the parser then interprets u:\folder 1 as drive=u:, path=\ and filename=folder 1

Echo Outputs When Writing to File

When I use this code to select a character from my list, it works just fine but when I write it to a file using:
echo %pwd%>>pwd.gen
It will some times put the word "ECHO" randomly in the middle of the strings generated. Here is an example:
jUrkunjcxC
ecRECHOsI5w0T
DmJfat13fT
UWXOysW7Gb
pPmS7138Ve
nFkh32ECHOJd1
You can see it appears in line 2 and 6. This only happens about 20% of the time.
Here is the code:
#echo off
title Password Generator
color f0
:firstRun
set /a cnt=0
cls
setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
set /p Len=What length should the password be?
set /a Len=%Len%-1
cls
set /p Amt=How many would you like to generate?
cls
goto start
:start
set alfanum=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
set pwd=
FOR /L %%b IN (0, 1, %Len%) DO (
SET /A rnd_num=!RANDOM! * 62 / 32768 + 1
for /F %%c in ('echo %%alfanum:~!rnd_num!^,1%%') do set pwd=!pwd!%%c
)
echo %pwd%>> pwd.gen
set /a cnt=%cnt%+1
if %cnt%==%Amt% goto end
goto start
:end
cls
echo Done!
echo Results have been saved to "pwd.gen"
echo.
choice /c YN /m "Restart?"
if %errorlevel%==1 goto firstRun
:start
set alfanum=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
set pwd=
FOR /L %%b IN (0, 1, %Len%) DO (
SET /A rnd_num=!RANDOM! * 62 / 32768 + 1
for /F %%c in ('echo %%alfanum:~!rnd_num!^,1%%') do set pwd=!pwd!%%c
)
alfanum is 26+26+10 = 62 characters long.
RANDOM gives a random number from 0-32,767
When RANDOM is above 32240, rnd_num gets set to 62
string indexing starts at 0 not 1
the for /F %%c command indexes alfanum:~62,1~ which is an empty string
it calls echo with no parameter, which prints ECHO is on. instead of returning a single character
for /F defaults to splitting strings with a space delimiter, which separates out the first word
%%c becomes ECHO
you add ECHO into the password.
This is a combination of a couple of things. While I'm not totally clear about the inner workings of the whole thing, I know what's causing it and how to fix it.
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 is 62 characters long. However, substrings in batch start with 0, so valid numbers go from 0 to 61. For whatever reason, an index-out-of-range combined with the ^ in 'echo %%alfanum:~!rnd_num!^,1%%' is causing the word ECHO to be displayed.
To get around this, simply don't add 1 when calculating rnd_num.
SET /A rnd_num=!RANDOM! * 62 / 32768

DOS command find space in filename starting from right

Is there a command that will return the position of a the first space found in a filename starting from the right?
Example:
"my filename.txt" would return 13
"my file name.txt" would return 9
Thanks
#ECHO Off
SETLOCAL
set "var=my filename.txt"
:: here call the function. returns "length"
CALL :mylength
ECHO Length is %length% ERRORLEVEL is %errorlevel%
GOTO :EOF
:mylength
setlocal
SET /a length=0
:mylenloop
IF "%var:~-1%" neq " " SET "var=%var:~0,-1%"&SET /a length+=1&GOTO mylenloop
endlocal&SET length=%length%
:: optional : set errorlevel to the length as well
EXIT /b %length%
This function returns the length of the string after the space. If you want to include the space, simply change SET /a length=0 to SET /a length=1.
You don't say what to do if there are no spaces in the string, but this solution works where there is more than one space.
PowerShell:
PS C:\> $s = "test string 1"
PS C:\> $s.LastIndexOf(" ")
11
There is no direct batch command, but it is not hard to compute. You can use find/replace to remove everything up through the first space. Then all you need do is compute the remaining length and add 1.
I recommend the good performing :strlen function
#echo off
setlocal enableDelayedExpansion
set "var=my filename.txt"
set "test=!var:* =!"
call :strlen pos test
set /a pos+=1
echo result=%pos%
exit /b
:strlen <resultVar> <stringVar>
setlocal EnableDelayedExpansion
set "s=!%~2!#"
set "len=0"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
endlocal & set "%~1=%len%"
exit /b

Batch: count the number of digits in a variable

I want to find a way to know the number of digits in variable. For now, I'm trying to use this code. In this example, %var% is the variable that I need to know the number of digits it has.
set x=1
set var=12345
:LOOP
set temp=%var:~0,%x%%
if %temp%==%var% goto END
set x=%x%+1
goto LOOP
:END
Theoretically, at the end of the code %x% would be the number of digits %var% has. However, it doesn't work. I found out the problem is at the 3rd line. I modified the code to diagnose:
set x=1
set var=12345
:LOOP
set temp=%var:~0,%x%%
echo %temp%
pause
if %temp%==%var% goto END
set x=%x%+1
goto LOOP
:END
The result echoed was:
x%%
Can anyone pinpoint my mistake or give an alternative solution to determine the number of digits in a variable?
Here's a short solution, that only works for numeric variables:
set /a Log=1%var:~1%-%var:~1% -0
set /a Len=%Log:0=+1%
The variable %Len% will contain the number of digits in %var%.
Explanation
The basic idea is to convert the first digit to 1, and the rest of them (the 'trailing' digits) to 0's. Then we can use the string replacement function to replace all the 0's with +1 giving us 1+1+1.... and evaluate the string as an arithmetic expression. This will give us the total number of digits.
The 'trailing' digits can be gotten using %var:~1% and we convert them to 0 by subtracting them from the variable itself: 45678 - 5678 gives 40000 etc. However, the above code subtracts them from 1%var:~1% instead, in order to replace the first digit with 1 (i.e. 1 followed by the 'trailing' digits).
The reason for the extra -0 is in case %var% only has one digit, for example 7. In that case, the expression 1%var:~1%-%var:~1% would evaluate to 1- and the shell would complain: Missing operand. The -0 ensures that we always have a valid expression.
Now that we've converted the variable in to the proper form into %Log%, we can replace every occurrence of 0 with +1using %Log:0=+1% and evaluate the resulting expression using set /a, giving us our final result.
The main problem with your code is
set temp=%var:~0,%x%%
This does not work. The parser is not able to properly determine what percent sign belongs to what variable. You can enable delayed expansion and write it as
set "temp=!var:~0,%x%!"
For alternative versions, to handle any length string, any of the posted answers will work.
For a simpler solution, if you are sure the string is under 10 characters, then this is an alternative
set "x=0123456789%var%"
set "x=%x:~-10,1%"
As there is no build in function for string length, you can write your own function.
#echo off
setlocal
set "myString=abcdef!%%^^()^!"
call :strlen result myString
echo %result%
goto :eof
:strlen <resultVar> <stringVar>
(
setlocal EnableDelayedExpansion
set "s=!%~2!#"
set "len=0"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
)
(
endlocal
set "%~1=%len%"
exit /b
)
This function needs always 13 loops, instead of a simple strlen function which needs strlen-loops.
It handles all characters.
Source: How do you get the string length in a batch file?
Your are trying to do this loop :
#Echo Off
Set /P VrStr=Enter your string :
:Loop
If "%VrStr%" EQU "" Goto EndLoop
Set VrStr=%VrStr:~0,-1%
Set /A VrLgr+=1
Goto Loop
:EndLoop
Echo Number of char: %VrLgr%
Pause
You can use this to :
#echo off
setlocal EnableDelayedExpansion
Set /P $Tstring=Enter your string:
for /l %%a in (0,1,9000) do (set $t=!$Tstring:~%%a,1!&if not defined $t (echo [NB OF CHAR =] %%a&pause&exit /b))
pause

Batch For loop array

Okay so here is what I have.
#echo off
setLocal EnableDelayedExpansion
:begin
set /a M=0
set /a number=0
set /p Input=You:
echo %Input% >> UIS
for /F "tokens=1 delims= " %%i in ("%Input%") do (
set /a M+=1
set i!M!=%%i
)
del UIS 1>nul 2>nul
:loop
set /a number+=1
set invar=!i%number%!
echo %invar%
pause > nul
goto loop
Say, for example, the Input string was "Lol this is my input string"
I want the for loop to set i!M! where M = 1 to "Lol", where M = 2 i!M! is "this" and where M = 3 i!M! is "is" and so on. Now, of course, this can't go on forever, so even if I have to stop when M = 25 or something, and say the string was only 23 words long. Then when M = 24 and 25 then i!M! is simply null or undefined.
Any help is appreciated, thank you.
for /f reads line by line, not word by word.
Here's an answer proposed at How to split a string in a Windows batch file? and modified for your situation:
#echo off
setlocal ENABLEDELAYEDEXPANSION
REM Set a string with an arbitrary number of substrings separated by semi colons
set teststring=Lol this is my input string
set M=0
REM Do something with each substring
:stringLOOP
REM Stop when the string is empty
if "!teststring!" EQU "" goto displayloop
for /f "delims= " %%a in ("!teststring!") do set substring=%%a
set /a M+=1
set i!M!=!substring!
REM Now strip off the leading substring
:striploop
set stripchar=!teststring:~0,1!
set teststring=!teststring:~1!
if "!teststring!" EQU "" goto stringloop
if "!stripchar!" NEQ " " goto striploop
goto stringloop
:displayloop
set /a number+=1
set invar=!i%number%!
echo %invar%
pause > nul
goto displayloop
endlocal
for /F command divide a line in a definite number of tokens that must be processed at once via different replaceable parameters (%%i, %%j, etc). Plain for command divide a line in an undefined number of words (separated by space, comma, semicolon or equal-sign) that are processed one by one in an iterative loop. This way, you just need to change this for:
for /F "tokens=1 delims= " %%i in ("%Input%") do (
by this one:
for %%i in (%Input%) do (
PS - I suggest you to write the array in the standard form, enclosing the subscript in square brackets; it is clearer this way:
set i[!M!]=%%i
or
set invar=!i[%number%]!

Resources