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
Related
I would like write a batch file to count the number of occurrences of a specific character in each line of a text file.
For example, the count of \ in the string "aa\bb\cc\dd\" would be 4.
The find and the findstr show only the number of lines which is contains the exact character.
You might try the following script, providing the input string as (quoted) command line argument:
set "STRING=%~1$"
set STRING="%STRING:\=" "%"
set /A "COUNT=-1"
for %%E in (%STRING%) do set /A "COUNT+=1"
echo Count of `\`: %COUNT%
This replaces every character to be counted by " + SPACE + " and encloses the entire string in between "", so the input string aa\bb\cc\dd\ becomes "aa" "bb" "cc" "dd" "". The resulting string is fed into a for loop that recognises individual items to iterate through -- five in this case. The counter variable COUNT is initialised with a value of -1, so the result is not the number of iterated items but the separators, namely the \ characters present in the original string.
This approach fails if the string contains ? or * characters. It would also fail in case the character to count is one of the following: ", %, =, *, ~.
#echo off
setlocal
set "string=aa\bb\cc\dd\"
set "count=-1"
for %%a in ("%string:\=" "%") do set /A count+=1
echo %count%
This method works correctly as long as the string don't include wild-card characters: *?; if this is required, I would use the same npocmaka's method, but written in a simpler way:
#echo off
setlocal EnableDelayedExpansion
set "string=aa\bb\cc\dd\"
set "str=A%string%Z"
set "count=-1"
for /F "delims=" %%a in (^"!str:\^=^"^
% Do NOT remove this line %
^"!^") do (
set /A count+=1
)
echo %count%
While slow, you can try with this
#echo off
setlocal enableextensions disabledelayedexpansion
set "inputFile=input.txt"
set "searchChar=\"
for /f "delims=" %%a in ('
findstr /n "^" "%inputFile%"
') do for /f "delims=:" %%b in ("%%~a") do (
set "line=%%a"
for /f %%c in ('
cmd /u /v /e /q /c"(echo(!line:*:=!)"^|find /c "%searchChar%"
') do echo Line %%b has %%c characters
)
The input file is readed using findstr /n to get all the lines in the file with a number prefix (both for output "decoration" and to ensure all the lines in the file are processed). Each line is processed inside a pipe, from cmd to find. The cmd instance is started with unicode output (/u) so when the readed line is echoed, the output will be a two bytes sequence for each input character, one of them a 0x0 ASCII character. The find command sees the 0 as a line terminator, so we get each character in the input line as one separated line. Now, the find command counts in how many lines the searched character happens.
#ECHO OFF
SETLOCAL
SET "String=a\b\c\\\\d"
CALL :count "%string%" \
ECHO %tally%
GOTO :EOF
:count
SETLOCAL enabledelayedexpansion
SET /a tally=0
SET "$2=%~1"
:cloop
SET "$1=%$2%"
SET "$2=!$1:*%2=!"
IF "%$1%" neq "%$2%" SET /a tally+=1&GOTO cloop
endlocal&SET tally=%tally%
GOTO :eof
Here's a way to count particular characters in a string. It won't work for the usual suspects.
here's one way:
#echo off
:checkCountOf string countOf [rtnrVar]
:: checks count of a substring in a string
setlocal EnableDelayedExpansion
set "string=aa"
set "string=%~1"
set "checkCountOf=%~2"
if "%~1" equ "" (
if "%~3" neq "" (
endlocal & (
echo 0
set "%~3=0"
exit /b 0
)
) else (
endlocal & (
echo 0
exit /b 0
)
)
)
if "!checkCountOf!" equ "$" (
set "string=#%string%#"
set "string=!string:%checkCountOf%%checkCountOf%=#%checkCountOf%#%checkCountOf%#!"
) else (
set "string=$%string%$"
set "string=!string:%checkCountOf%%checkCountOf%=$%checkCountOf%$%checkCountOf%$!"
)
set LF=^
rem ** Two empty lines are required
set /a counter=0
for %%L in ("!LF!") DO (
for /f "delims=" %%R in ("!checkCountOf!") do (
set "var=!string:%%~R%%~R=%%~L!"
set "var=!var:%%~R=%%~L!"
for /f "tokens=* delims=" %%# in ("!var!") do (
set /a counter=counter+1
)
)
)
if !counter! gtr 0 (
set /a counter=counter-1
)
if "%~3" neq "" (
endlocal & (
echo %counter%
set "%~3=%counter%"
)
) else (
endlocal & (
echo %counter%
)
)
you can call it like:
call ::checkCountOf "/aa/b/c/" "/" slashes
echo %slashes%
exit /b %errorlevel%
wont work with some special characters (",~ and !)
You can also use replacement and the :strlen function
Not tested extensively but works with your example.
#ECHO OFF
SETLOCAL disabledelayedexpansion
SET "String=\a\b\c\\\\d\\"
set "previous=%string%"
set /a count=0
:loop
set "newstg=%previous:*\=%"
IF NOT "%previous%"=="%newstg%" (
set /a count+=1
set "previous=%newstg%"
IF DEFINED previous goto loop
)
echo %count%
pause
GOTO :eof
Here is one more option. I don't think this is bullet proof with poison characters.
#ECHO OFF
SETLOCAL disabledelayedexpansion
SET "String=\\a\b\c\\\\d\\"
set i=0
set "x=%string%"
set "x=%x:\=" & set /A i+=1 & set "x=%"
echo %i%
pause
I've been working on a Batch polygon area calculator and I got a problem.
I need to multiply 2 variables, but sometimes it return a negative number if the two positive variables are large.
Here's an example: 999999*999999 returns -729379967.
Code goes below:
REM Calc square area
:PolySqu
Cls
Echo Polygon Area Calculator
For /L %%P In (1,1,57) Do Echo.
Set /P "InputPolygonCalSqu=Enter one of the line's length in cm :"
Set /A SquArea=InputPolygonCalSqu * InputPolygonCalSqu
Cls
Echo Polygon Area Calculator
For /L %%P In (1,1,57) Do Echo.
Echo The area of this square is %SquArea% cm2.
Pause
Goto :PolygonCal
It seemed the command
Set /A SquArea="InputPolygonCalSqu * InputPolygonCalSqu
doesn't calculate properly.
As others already pointed out, a batch-file natively supports 32-bit signed integer arithmetics only.
The following code constitutes a work-around for multiplying non-negative numbers greater than the limit of 232 − 1 = 2147483647, using pure batch-file commands (let us call it multiply.bat):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define arguments here:
set "NUM1=%~1"
set "NUM2=%~2"
set "NUM3=%~3"
set "NUM4=%~4"
if defined NUM1 set "NUM1=%NUM1:"=""%
if defined NUM2 set "NUM2=%NUM2:"=""%
if defined NUM3 set "NUM3=%NUM3:"=%
call :VAL_ARGS NUM1 NUM2 NUM4 || exit /B 1
rem // Define constants here:
set /A "DIG=4" & set "PAD="
setlocal EnableDelayedExpansion
for /L %%J in (1,1,%DIG%) do set "PAD=!PAD!0"
endlocal & set "PAD=%PAD%"
rem // Determine string lengths:
call :STR_LEN LEN1 NUM1
call :STR_LEN LEN2 NUM2
set /A "LEN1=(LEN1-1)/DIG*DIG"
set /A "LEN2=(LEN2-1)/DIG*DIG"
set /A "LIM=LEN1+LEN2+DIG"
for /L %%I in (0,%DIG%,%LIM%) do set /A "RES[%%I]=0"
rem // Perform block-wise multiplication:
setlocal EnableDelayedExpansion
for /L %%J in (0,%DIG%,%LEN2%) do (
for /L %%I in (0,%DIG%,%LEN1%) do (
set /A "IDX=%%I+%%J"
if %%I EQU 0 (set "AUX1=-%DIG%") else (
set /A "AUX1=%DIG%+%%I" & set "AUX1=-!AUX1!,-%%I"
)
if %%J EQU 0 (set "AUX2=-%DIG%") else (
set /A "AUX2=%DIG%+%%J" & set "AUX2=-!AUX2!,-%%J"
)
for /F "tokens=1,2" %%M in ("!AUX1! !AUX2!") do (
set "AUX1=!NUM1:~%%M!" & set "AUX2=!NUM2:~%%N!"
)
call :NO_LEAD0 AUX1 !AUX1!
call :NO_LEAD0 AUX2 !AUX2!
set /A "RES[!IDX!]+=AUX1*AUX2"
set /A "NXT=IDX+DIG, DIT=DIG*2"
for /F "tokens=1,2,3" %%M in ("!IDX! !NXT! !DIT!") do (
set "AUX=!RES[%%M]:~-%%O,-%DIG%!"
set /A "RES[%%N]+=AUX"
set "RES[%%M]=!RES[%%M]:~-%DIG%!"
call :NO_LEAD0 RES[%%M] !RES[%%M]!
)
)
)
rem // Build resulting product:
set "RES=" & set "AUX="
for /L %%I in (0,%DIG%,%LIM%) do (
set /A "RES[%%I]+=AUX"
set /A "NXT=%%I+DIG"
for /L %%J in (!NXT!,%DIG%,!NXT!) do (
set "AUX=!RES[%%I]:~-%%J,-%DIG%!"
)
set "RES[%%I]=%PAD%!RES[%%I]!"
set "RES=!RES[%%I]:~-%DIG%!!RES!"
)
endlocal & set "RES=%RES%"
call :NO_LEAD0 RES %RES%
rem // Return resulting product:
echo(%RES%
if defined NUM3 (
endlocal
set "%NUM3%=%RES%"
) else (
endlocal
)
exit /B
:NO_LEAD0 rtn_var val_num
rem // Remove leading zeros from a number:
for /F "tokens=* delims=0" %%Z in ("%~2") do (
set "%~1=%%Z" & if not defined %~1 set "%~1=0"
)
exit /B 0
:STR_LEN rtn_length ref_string
rem // Retrieve length of string:
setlocal EnableDelayedExpansion
set "STR=!%~2!"
if not defined STR (set /A LEN=0) else (set /A LEN=1)
for %%L in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if defined STR (
set "INT=!STR:~%%L!"
if not "!INT!"=="" set /A LEN+=%%L & set "STR=!INT!"
)
)
endlocal & set "%~1=%LEN%"
exit /B 0
:VAL_ARGS ref_arg1 ref_arg2 ref_arg3
rem // Check arguments for validity:
if not defined %~1 >&2 echo ERROR: too few arguments given! & exit /B 1
if not defined %~2 >&2 echo ERROR: too few arguments given! & exit /B 1
if defined %~3 >&2 echo ERROR: too many arguments given! & exit /B 1
(call echo "%%%~1%%" | > nul findstr /R /C:"^\"[0-9][0-9]*\" $") || (
>&2 echo ERROR: argument 1 is not purely numeric! & exit /B 1
)
(call echo "%%%~2%%" | > nul findstr /R /C:"^\"[0-9][0-9]*\" $") || (
>&2 echo ERROR: argument 2 is not purely numeric! & exit /B 1
)
exit /B 0
To use it provide the two numbers to multiply as command line arguments; for instance:
multiply.bat 999999 999999
The resulting product is returned on the console:
999998000001
If you provide a third argument, the product is assigned to a variable with that name; for example:
multiply.bat 999999 999999 SquArea
This sets variable SquArea to the resulting value. The latter is also still returned on the console.
To silently assign the variable without any additional console output, redirect it to the nul device:
multiply.bat 999999 999999 SquArea > nul
Batch uses 32bit integers for storing numbers. This gives them a maximum size of 2^31 - 1 = 2,147,483,647.
999,999 * 999,999 = 999,998,000,001 which is larger than 2,147,483,647 therefore it "wraps" and starts from negatives.
This is a limitation of batch although there are some workarounds.
This might be useful
Can batch files not process large numbers?
The largest number batch can take is 2^31 - 1 = 2,147,483,647.
So again, it is starting back from negative and giving you that answer..
I noticed using pure batch, it would be somewhat to very difficult to implement operations that support the numbers beyond the 32-bit integer limit. So instead, I limited the numbers, so they won't overflow when multiplied.
REM Calc Square Area
:PolySqu
Cls
Echo Polygon Area Calculator
For /L %%P In (1,1,57) Do Echo.
Set /P "InputPolygonCalSqu=Enter one of the line's length in cm [Less then 40000] :"
If %InputPolygonCalSqu% GTR 40000 Goto :PolySqu
Set /A SquArea="InputPolygonCalSqu * InputPolygonCalSqu
Cls
Echo Polygon Area Calculator
For /L %%P In (1,1,57) Do Echo.
Echo The area of this square is %SquArea% cm2.
Pause
Goto :PolygonCal
I am trying to create a matrix this one works below) but is slow as it has to loop every number. So i was wondering if you can have multiple %random% %% 2 without just a "(random number) %2" being printed.
#echo off
color 0a
:a
set /a mat=%random% %% 2
echo |set /p=%mat%
goto a
I found this to be much faster:
#echo off
if not "%1" == "max" start /MAX cmd /c %0 max & exit/b
color 0a
setlocal EnableDelayedExpansion
:a
set "addition="
for /L %%i in (1,1,256) do (
set /a mat=!random! %% 2
set addition=!addition!!mat!
)
echo |set /p=%addition%
goto a
EDIT: I now see that the answer #Stephan links to in the comments is very similar, so credit goes to him too.
EDIT #2:
You might like this one too, has all kind of characters so it looks a bit more "matrixy"
#echo off
if not "%1" == "max" start /MAX cmd /c %0 max & exit/b
color a
setlocal EnableDelayedExpansion
set "CHARS=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789######$$$%%%%%%*** "
echo %CHARS%>x&for %%? in (x) do set /a strlength=%%~z? - 2&del x
:a
call :randomString addition 100 "%chars%" %strlength%
echo |set /p=%addition%
goto a
:randomString
set "length=%2"
set "CHARS=%~3"
if ["%CHARS%"]==[""] set "CHARS=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789######$$$%%%%%%***"
set "strlength=%4"
if [%strlength%]==[] echo %CHARS%>x&for %%? in (x) do set /a strlength=%%~z? - 2&del x
set "line="
for /L %%a in (1 1 %length%) do (
set /a "randnr=!random!%%!strlength!"
for /l %%n in (!randnr! 1 !randnr!) do set "line=!line!!CHARS:~%%n,1!"
)
set %1=%line%
exit /b
For extra effect add 6 spaces to the CHARS variable. Note that I also added a line to start it in full-screen mode.
The method below is the fastest possible way to solve this problem:
#echo off
setlocal EnableDelayedExpansion
rem Prepare the output for one line
set "bin=0101010101"
set "line="
for /L %%i in (1,1,79) do (
set "line=!line!%%bin:~^!random:~-1^!,1%%"
)
for /L %%i in () do call echo %line%
I'm trying to reverse "hello" to "olleh". But the output shows "ooooo".
I think !string:~%back%,1! is the problem, because when I use echo to test the value of back is decreasing or not, it works, but it doesn't work in substring, so it always get the last character of the string (-1,1).
#echo off
set string=hello
set temp_string=%string%
set /a string_length=0
:find_length
if defined temp_string (
set temp_string=%temp_string:~1%
set /a string_length+=1
goto :find_length
)
:loop
setlocal enabledelayedexpansion
set /a back=-1
for /l %%a in (1,1,!string_length!) do (
set reverse_string=!string:~%back%,1!!reverse_string!
set /a back-=1
)
echo !reverse_string!
pause >nul
As TripeHound commented, %back% needs to be delayed. What you should do is use the for /L loop value of %%a to in place of %back%. (No sense decrementing a variable manually when one's already being decremented for you as a part of the for /L loop, right?)
for /l %%a in (%string_length%,-1,0) do (
call set "reverse_string=!reverse_string!!string:~%%a,1!"
)
goto loops are not very efficient. If you've got a long string you're going to reverse, there'll be a noticeable pause while you count its length if you goto :label for each character. The fastest way I've found to get the length of a string 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
Put it all together like this:
#echo off
setlocal
set "string=%*"
call :length string_length "%string%"
setlocal enabledelayedexpansion
for /l %%a in (%string_length%,-1,0) do (
set "reverse_string=!reverse_string!!string:~%%a,1!"
)
echo(!reverse_string!
pause >nul
exit /b 0
: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 output:
command: test.bat The quick brown fox
result: xof nworb kciuq ehT
The problem is that %back% is being used without delayed expansion, so will always have the value -1. Replacing the end of your code with:
set /a back=-1
set /a count=1
:repeat
if %count% gtr %string_length% goto :report
set reverse_string=!string:~%back%,1!!reverse_string!
set /a back-=1
set /a count+=1
goto :repeat
:report
echo !reverse_string!
Will do the trick.
You cannot just use !back! because you need the contrast of !...! and %...% to have a variable index, so you'll have to go back to an old-fashioned :loop construct so %back% gets updated each time around.
As described at this post:
"To get the value of a substring when the index change inside FOR/IF enclose the substring in double percents and precede the command with call. For example:
for /l %%a in (1,1,!string_length!) do (
call set reverse_string=%%string:~!back!,1%%!reverse_string!
set /a back-=1
)
Another way to achieve previous process is using an additional FOR command to change the delayed expansion of the index by an equivalent replaceable parameter, and then use the delayed expansion for the substring. This method run faster than previous CALL:
for /l %%a in (1,1,!string_length!) do (
for %%b in (!back!) do (
set reverse_string=!string:~%%b,1!!reverse_string!
)
set /a back-=1
)
"
However, it is not efficient to first loop thru the string just to count the characters, and then loop again to reverse they. I think the method below should be the fastest one:
#echo off
setlocal EnableDelayedExpansion
set maxLength=80
set string=hello
set "reverse="
for /L %%i in (1,1,%maxLength%) do (
set "reverse=!string:~0,1!!reverse!"
set "string=!string:~1!"
if not defined string goto break
)
:break
echo %reverse%
Here's another algorithm:
#echo off
call :reverse "The quick brown fox"
echo %output%
pause & exit
:reverse
setlocal enableDelayedExpansion
set string=%~1
set index=0
:loopchar
set char=!string:~%index%,1!
if "!char!"=="" endlocal & set output=%output% & exit /b
set output=!char!!output!
set /a index+=1
goto loopchar
"Tricks":
using %index% inside ! to expand its current value instead of call with %% (#Aacini)
export of !output! from the local scope by reassigning it on the same line as endlocal.
I want to read a CSV file line by line and echo something different if the length of the line is 7999.
I manage to do something as below, which reads each line and checks the number of character for each line, but the issue is that I am getting no value in %result% and echo(%result% prints a blank value. Any idea what am I doing wrong here? Thanks
#echo off
setlocal
for /f "tokens=* delims= " %%a in (REPORTS.csv) do (
set "line=%%a"
call :strlen result line
echo(%result%
if %result% EQU 7999 (
echo %%a
echo(short=%result%
) else (
echo %%a
echo(long=%result%
)
pause
)
: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
)
Put this section into another subroutine, similar to :strlen
echo(%result%
if %result% EQU 7999 (
echo %%a
echo(short=%result%
) else (
echo %%a
echo(long=%result%
)
Note also that your main routine will continue into your subroutine when finished, so at end-of-file(reports.csv) the batch will execute :strlen one final time and exit through the EXIT
I'd recommend adding a
GOTO :EOF
Immediately before the :strlen label. This is understood by the processor to go to end-of-physiacl-file (the colon is required)
When a compound statement enclosed in parentheses is to be executed,
the statement is first parsed from the open parenthesis all of the
way to the matching close-parenthesis.
At this time, any %var% is replaced by that var's value from the
environment AT THE TIME IT IS PARSED (ie its PARSE-TIME value.)
THEN if the statement seems valid, it is executed.
There are three common ways of accessing the RUN-TIME value of the
variable (as a FOR loop executes, for instance.)
1/ SETLOCAL ENABLEDELAYEDEXPANSION which switches to a mode where
!var! may be used to access the runtime value of var
2/ CALL set var2=%%var%% to set the value of var2 from the
runtime value of var
3/ Executing a subroutine, internal or external within which %var%
will be the runtime value.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%i IN (1 2 3) DO (
ECHO START of run %%i
ECHO using ^!time^! : !time! - PARSE TIME was %time%
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
timeout /t 5
ECHO using ^!time^! : !time!
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
ECHO END of run %%i
ECHO.
)
GOTO :eof
:report
ECHO :report says TIME is %TIME%
GOTO :eof
A few items to note:
The instruction
IF ERRORLEVEL n echo errorlevel is n OR GREATER
ALWAYS interprets the RUN-TIME value of ERRORLEVEL
IF SET VAR ALWAYS interprets the RUN-TIME value of VAR
The magic variables like ERRORLEVEL and TIME should never
be SET. If you execute
SET ERRORLEVEL=dumb
then ERRORLEVEL will adopt the value dumb because the current
value in the environment takes priority over the system-assigned value.
You should use DelayedExpansion in if and for loops and take care of the brackets:
#echo off
setlocal enabledelayedexpansion
for /f "tokens=* delims= " %%a in (REPORTS.csv) do (
set "line=%%a"
call :strlen result line
echo.!result!
if !result! EQU 7999 (
echo.%%a
echo.short=!result!
) else (
echo.%%a
echo.long=!result!
)
)
pause
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
Your code doesn't ever work in many areas.