Cornan
Just created this stringlength.cmd file. All have my permission to use and SS64.org has my permission to post it
Usage: CALL stringlength any parameters
Return: _stringlength variable set to total parameter length
Code: Select all
@ECHO OFF
SETLOCAL
SET WhatsLeft=%*
SET Count=0
:TestIt
IF "%WhatsLeft%"=="" GOTO RETURN
SET WhatsLeft=%WhatsLeft:~,-1%
SET /A Count=%Count%+1
GOTO TestIt
:RETURN
ENDLOCAL & SET _stringlength=%Count%
#2 20 Jan 2013 17:55
npocmaka
An pretty old topic ,but I also needed this.
I've used almost the same thing , but in this above I see two potential problems - If the string contains double quotes the if checks may fail , and there's no initial check if the string is empty.Here I'm trying to deal with this (but not completely works with the second parameter may I should remove it as an option):
Code: Select all
:strlen [%1 -string, %2 variable to store result in]
setlocal
set string=%~1
rem if the string contains double quotes it will harm the IF checks
set string=%string:"=.%
set var=%~2
set /a counter=0
if "%string%" equ "" goto :return
:loop
rem counter will hold the 1/2 of the string lenght
set /a counter=%counter%+1
set string=%string:~1,-1%
if "%string%" equ "" (
goto :endloop
)
goto :loop
:endloop
set string=%~1
set string=%string:"=.%
set /a counter=2*%counter%-1
rem checking if the string is with even or with uneven lenght
call set string=%%string:~%counter%%%
if "%string%" neq "" set /a counter=%counter%+1
echo %counter%
:return
endlocal & set %var%=%counter% >nul 2>&1
----------------------------
#3 21 Jan 2013 01:39
dbenham
Here is a survey of algorithms for computing string length. One general technique in common is to use delayed expansion so that the routines compute the correct length no matter what characters are contained within the string.
Each of the routines below has identical functionality and nearly identical limits. The only slight differences are the maximum length that can be computed. All methods approach the 8191 maximum byte length, but the actual limit varies slightly.
Each routine requires the name of a variable as the first parameter. Each routine measures the length of the string value stored within the named variable, and properly computes 0 if the variable is not defined.
Each routine optionally takes the name of a return variable as the 2nd parameter, where the result is stored. If the return variable is not specified, then the result is ECHOed to stdout.
I placed each routine in its own file to get accurate timings. For each routine, I report the amount of seconds it takes to compute the length of various string lengths 1000 times.
:strLen1
The simplest (and generally slowest) is a brute force linear technique that is basically the same as the original post in this thread. It is fine for short strings, but is awful for even moderately long strings. The problem is that GOTO is quite slow. The routine is even worse if the routine is embedded in a long batch file, because batch must scan the entire file to loop back - the longer the file, the worse the performance.
Code: Select all
:strlen1 StrVar [RtnVar]
setlocal enableDelayedExpansion
set "s=!%~1!"
set len=0
:strLenLoop
if defined s (
set "s=!s:~1!"
set /a len+=1
goto :strlenLoop
)
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
0 4.35
5 7.98
326 239.5
8184 7147.
:strLen2
This simple optimization uses a FOR /L loop instead of GOTO to perform the brute force linear length search. It makes short strings a bit slower, but is a major improvement for moderately long strings. It is still quite slow for very long strings.
Code: Select all
:strlen4 StrVar [RtnVar]
setlocal enableDelayedExpansion
set "s=!%~1!"
set len=0
if defined s for /l %%N in (1 1 8192) do if "!s:~%%N,1!" equ "" (
set len=%%N
goto :break
)
:break
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
0 4.28
5 82.90
326 86.15
8184 429.0
:strLen3
Here is the first algorithm that has decent performance that does not degrade with long strings. It uses the rarely used FINDSTR /O option. The limiting performance factor is the startup time for the external FINDSTR command and the extra time it takes to launch 2 CMD threads.
Code: Select all
:strLen3 StrVar [RtnVar]
setlocal disableDelayedExpansion
set len=0
if defined %~1 for /f "delims=:" %%N in (
'"(cmd /v:on /c echo(!%~1!&echo()|findstr /o ^^"'
) do set /a "len=%%N-3"
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
0 6.33
5 40.23
326 40.05
8184 41.86
:strLen4
The remainder of the algorithms all have excellent performance. This one is amazingly simple. Its only drawback in some people's eyes is its use of a temporary file.
Code: Select all
:strlen4 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "tempFile=%temp%\strlen%random%.tmp"
echo(!%~1!>"%tempFile%"
for %%F in ("%tempFile%") do set /a len=%%~zF-2
del "%tempFile%"
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
0 6.33
5 6.44
326 6.31
8184 7.27
:strLen5
This is a minor optimization of :strLen4 that on my machine gives the best performance. The only difference is it assumes the name of the temporary file is already initialized, and it does not bother deleting the temporary file when finished. The same temp file gets reused for each call.
Code: Select all
:strlen2 StrVar [RtnVar]
setlocal EnableDelayedExpansion
echo(!%~1!>"%tempFile%"
for %%F in ("%tempFile%") do set /a len=%%~zF-2
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
0 4.73
5 4.72
326 4.75
8184 5.62
:strLen6
This is a very fast algorithm developed at DosTips. It uses a binary search to detect the length of the string. It only requires 13 iterations for any length string supported by batch. On some machines, this routine is slightly faster than :strlen4, but on my machine it is a bit slower. The biggest advantage over :strLen4 and :strLen5 is this routine does not need a temp file.
Code: Select all
:strlen1 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "s=#!%~1!"
set "len=0"
for /l %%A in (12,-1,0) do (
set /a "len|=1<<%%A"
for %%B in (!len!) do if "!s:~%%B,1!"=="" set /a "len&=~1<<%%A"
)
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
0 8.04
5 7.96
326 7.66
8184 9.27
:strLen7
This is a minor optimization of :strLen6 that is slightly faster. Still not quite as fast as :strLen5 on my machine.
Code: Select all
:strlen0 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "s=#!%~1!"
set "len=0"
for %%N in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%N,1!" neq "" (
set /a "len+=%%N"
set "s=!s:~%%N!"
)
)
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
0 4.53
5 5.07
326 5.56
8184 7.84
Dave Benham
Last edited by dbenham (21 Jan 2013 02:12)
----------------------------
#21 Jan 2013 15:34
npocmaka
Hohoooo..
Cool.
A small improvement over strlen2 that uses again the slicing from two directions.
I didn't performed any performance checks yet but hope it will be faster:
Code: Select all
:strlen2.5 StrVar [RtnVar]
setlocal enableDelayedExpansion
set "s=!%~1!"
set len=0
if defined s for /l %%N in (1,1,8192) do if "!s:~%%N,-%%N!" equ "" (
set len=%%N
goto :break
)
:break
set /a len=2*!len!-1
for %%E in (!len!) do (
set s=!s:~%%E!
)
if "!s!" neq "" set /a len=!len!+1
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
----------------------------
# 21 Jan 2013 20:15
npocmaka
and the same idea as the 7th function but based on powers of 3:
Code: Select all
:strlen0.3 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "s=#!%~1!"
set "len=0"
for %%A in ( 6561 2187 729 243 81 27 9 3 1) do (
set /A mod=2*%%A
for %%Z in (!mod!) do (
if !mod! GTR 8190 (
set mod=8190
)
if "!s:~%%Z,1!" neq "" (
set /a "len+=%%Z"
set "s=!s:~%%Z!"
) else (
if "!s:~%%A,1!" neq "" (
set /a "len+=%%A"
set "s=!s:~%%A!"
)
)
)
)
![Smile :-)](./images/smilies/icon_e_smile.gif)
(may be it's worth to try also with 4 - it will require one more nested IF and one more nested FOR and the IF checks will be almost the same number as the loops in the outer FOR.Also could try to put two item in inner FOR instead of nested IF but this will require one GOTO ....)
Last edited by npocmaka (21 Sep 2014 18:01)
----------------------------
# 25 Jan 2013 10:27
npocmaka
and one more - just for fun again based on strlen2 (but with 5 hops on each iteration):
Code: Select all
:strlen2.9 StrVar [RtnVar]
setlocal enableDelayedExpansion
set "s=!%~1!"
set len=0
if defined s for /l %%N in (1,5,8192) do if "!s:~%%N,-%%N!" equ "" (
set len=%%N
goto :break
)
:break
if !len! gtr 1 (
set /a len=2*!len!-12
for %%E in (!len!) do (
set s=!s:~%%E!
)
)
if defined s (
if "!s:~0!" neq "" set /a len=!len!+1
if "!s:~1!" neq "" set /a len=!len!+1
if "!s:~2!" neq "" set /a len=!len!+1
if "!s:~3!" neq "" set /a len=!len!+1
if "!s:~4!" neq "" set /a len=!len!+1
if "!s:~5!" neq "" set /a len=!len!+1
if "!s:~6!" neq "" set /a len=!len!+1
if "!s:~7!" neq "" set /a len=!len!+1
if "!s:~8!" neq "" set /a len=!len!+1
if "!s:~9!" neq "" set /a len=!len!+1
if "!s:~10!" neq "" set /a len=!len!+1
if "!s:~11!" neq "" set /a len=!len!+1
)
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
As I was curious about how fast are mine - here's an evaluation script:
Code: Select all
::::::::::::
::
:: strlen evaluation
::
:::::::::::
@echo off
set str1=1
set str5=11111
set str10=1111111111
set str50=11111111111111111111111111111111111111111111111111
set str100=1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
set str1000=1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
set str5000=11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
echo\
echo -----------------
echo --- strlen2.9 ---
echo -----------------
echo\
call :timecalc strlen2.9 str5
call :timecalc strlen2.9 str1
call :timecalc strlen2.9 str10
call :timecalc strlen2.9 str50
call :timecalc strlen2.9 str100
call :timecalc strlen2.9 str1000
call :timecalc strlen2.9 str5000
echo\
echo -----------------
echo --- strlen1 ---
echo -----------------
echo\
call :timecalc strlen1 str5
call :timecalc strlen1 str1
call :timecalc strlen1 str10
call :timecalc strlen1 str50
call :timecalc strlen1 str100
call :timecalc strlen1 str1000
call :timecalc strlen1 str5000
echo\
echo -----------------
echo --- strlen2.5 ---
echo -----------------
echo\
call :timecalc strlen2.5 str5
call :timecalc strlen2.5 str1
call :timecalc strlen2.5 str10
call :timecalc strlen2.5 str50
call :timecalc strlen2.5 str100
call :timecalc strlen2.5 str1000
call :timecalc strlen2.5 str5000
echo\
echo -----------------
echo --- strlen2 ---
echo -----------------
echo\
call :timecalc strlen2 str5
call :timecalc strlen2 str1
call :timecalc strlen2 str10
call :timecalc strlen2 str50
call :timecalc strlen2 str100
call :timecalc strlen2 str1000
call :timecalc strlen2 str5000
echo\
echo -----------------
echo --- strlen3 ---
echo -----------------
echo\
call :timecalc strlen3 str5
call :timecalc strlen3 str1
call :timecalc strlen3 str10
call :timecalc strlen3 str50
call :timecalc strlen3 str100
call :timecalc strlen3 str1000
call :timecalc strlen3 str5000
echo\
echo -----------------
echo --- strlen0.3 ---
echo -----------------
echo\
call :timecalc strlen0.3 str5
call :timecalc strlen0.3 str1
call :timecalc strlen0.3 str10
call :timecalc strlen0.3 str50
call :timecalc strlen0.3 str100
call :timecalc strlen0.3 str1000
call :timecalc strlen0.3 str5000
echo\
echo -----------------
echo --- strlen0 ---
echo -----------------
echo\
call :timecalc strlen0 str5
call :timecalc strlen0 str1
call :timecalc strlen0 str10
call :timecalc strlen0 str50
call :timecalc strlen0 str100
call :timecalc strlen0 str1000
call :timecalc strlen0 str5000
goto :eof
:timecalc
set time1=%time%
call :%1 %2 len
set time2=%time%
call :gettimeinms %time1% ms1
call :gettimeinms %time2% ms2
if %ms1% gtr 15900 (
if %ms2% lss 11000 (
set /a ms2=%ms2%+6000
)
)
set /a "totalTime=%ms2%-%ms1%"
echo %2 -^> %totalTime% [ %time2% - %time1% ]
goto :eof
:gettimeinms
setlocal
for /f "tokens=3,4 delims=:." %%S in ( "%~1") do (
set ms=1%%S%%T
)
endlocal & set %~2=%ms%
goto :eof
::::::::::::::::::::::::::::::::::::::::
:: strlen1
:strlen1 StrVar [RtnVar]
setlocal enableDelayedExpansion
set "s=!%~1!"
set len=0
:strLenLoop
if defined s (
set "s=!s:~1!"
set /a len+=1
goto :strlenLoop
)
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
::length sec*1000
:: 0 4.35
:: 5 7.98
:: 326 239.5
:: 8184 7147.
::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
:: strlen4
:strlen4 StrVar [RtnVar]
setlocal enableDelayedExpansion
set "s=!%~1!"
set len=0
if defined s for /l %%N in (1 1 8192) do if "!s:~%%N,1!" equ "" (
set len=%%N
goto :break
)
:break
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
::length sec*1000
:: 0 4.28
:: 5 82.90
:: 326 86.15
:: 8184 429.0
:::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
:: strlen3
:strLen3 StrVar [RtnVar]
setlocal disableDelayedExpansion
set len=0
if defined %~1 for /f "delims=:" %%N in (
'"(cmd /v:on /c echo(!%~1!&echo()|findstr /o ^^"'
) do set /a "len=%%N-3"
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
::length sec*1000
:: 0 6.33
:: 5 40.23
:: 326 40.05
:: 8184 41.86
::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
:: strlen4
:strlen4 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "tempFile=%temp%\strlen%random%.tmp"
echo(!%~1!>"%tempFile%"
for %%F in ("%tempFile%") do set /a len=%%~zF-2
del "%tempFile%"
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
:: length sec*1000
:: 0 6.33
:: 5 6.44
:: 326 6.31
:: 8184 7.27
::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
:: strlen2
:strlen2 StrVar [RtnVar]
setlocal EnableDelayedExpansion
echo(!%~1!>"tempFile"
for %%F in ("tempFile") do set /a len=%%~zF-2
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
::length sec*1000
:: 0 4.73
:: 5 4.72
:: 326 4.75
:: 8184 5.62
::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
:: strlen1
:strlen1 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "s=#!%~1!"
set "len=0"
for /l %%A in (12,-1,0) do (
set /a "len|=1<<%%A"
for %%B in (!len!) do if "!s:~%%B,1!"=="" set /a "len&=~1<<%%A"
)
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
::length sec*1000
:: 0 8.04
:: 5 7.96
:: 326 7.66
:: 8184 9.27
::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
:: strlen0
:strlen0 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "s=#!%~1!"
set "len=0"
for %%N in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%N,1!" neq "" (
set /a "len+=%%N"
set "s=!s:~%%N!"
)
)
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
::length sec*1000
:: 0 4.53
:: 5 5.07
:: 326 5.56
:: 8184 7.84
::::::::::::::::::::::::::::::::::::::::
:: strlen2.5
:strlen2.5 StrVar [RtnVar]
setlocal enableDelayedExpansion
set "s=!%~1!"
set len=0
if defined s for /l %%N in (1,1,8192) do if "!s:~%%N,-%%N!" equ "" (
set len=%%N
goto :break
)
:break
set /a len=2*!len!-1
for %%E in (!len!) do (
set s=!s:~%%E!
)
if "!s!" neq "" set /a len=!len!+1
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
:: strlen2.9
:strlen2.9 StrVar [RtnVar]
setlocal enableDelayedExpansion
set "s=!%~1!"
set len=0
if defined s for /l %%N in (1,5,8192) do if "!s:~%%N,-%%N!" equ "" (
set len=%%N
goto :break
)
:break
if !len! gtr 1 (
set /a len=2*!len!-12
for %%E in (!len!) do (
set s=!s:~%%E!
)
)
if defined s (
if "!s:~0!" neq "" set /a len=!len!+1
if "!s:~1!" neq "" set /a len=!len!+1
if "!s:~2!" neq "" set /a len=!len!+1
if "!s:~3!" neq "" set /a len=!len!+1
if "!s:~4!" neq "" set /a len=!len!+1
if "!s:~5!" neq "" set /a len=!len!+1
if "!s:~6!" neq "" set /a len=!len!+1
if "!s:~7!" neq "" set /a len=!len!+1
if "!s:~8!" neq "" set /a len=!len!+1
if "!s:~9!" neq "" set /a len=!len!+1
if "!s:~10!" neq "" set /a len=!len!+1
if "!s:~11!" neq "" set /a len=!len!+1
)
endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
:: strlen0.3
:strlen0.3 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "s=#!%~1!"
set "len=0"
for %%A in (2187 729 243 81 27 9 3 1) do (
set /A mod=2*%%A
for %%Z in (!mod!) do (
if "!s:~%%Z,1!" neq "" (
set /a "len+=%%Z"
set "s=!s:~%%Z!"
) else (
if "!s:~%%A,1!" neq "" (
set /a "len+=%%A"
set "s=!s:~%%A!"
)
)
)
)
endlocal & if "%~2" neq "" (set %~2=%len%) else echo **%len%**
exit /b
::::::::::::::::::::::::::::::::::::::
and the output :
Code: Select all
-----------------
--- strlen2.9 ---
-----------------
str5 -> 2 [ 11:21:11.43 - 11:21:11.41 ]
str1 -> 2 [ 11:21:11.46 - 11:21:11.44 ]
str10 -> 2 [ 11:21:11.49 - 11:21:11.47 ]
str50 -> 2 [ 11:21:11.52 - 11:21:11.50 ]
str100 -> 2 [ 11:21:11.55 - 11:21:11.53 ]
str1000 -> 2 [ 11:21:11.58 - 11:21:11.56 ]
str5000 -> 3 [ 11:21:11.62 - 11:21:11.59 ]
-----------------
--- strlen1 ---
-----------------
str5 -> 2 [ 11:21:11.65 - 11:21:11.63 ]
str1 -> 0 [ 11:21:11.66 - 11:21:11.66 ]
str10 -> 3 [ 11:21:11.70 - 11:21:11.67 ]
str50 -> 15 [ 11:21:11.86 - 11:21:11.71 ]
str100 -> 28 [ 11:21:12.15 - 11:21:11.87 ]
str1000 -> 287 [ 11:21:15.03 - 11:21:12.16 ]
str5000 -> 1452 [ 11:21:29.56 - 11:21:15.04 ]
-----------------
--- strlen2.5 ---
-----------------
str5 -> 7 [ 11:21:29.64 - 11:21:29.57 ]
str1 -> 6 [ 11:21:29.71 - 11:21:29.65 ]
str10 -> 7 [ 11:21:29.79 - 11:21:29.72 ]
str50 -> 7 [ 11:21:29.87 - 11:21:29.80 ]
str100 -> 6 [ 11:21:29.94 - 11:21:29.88 ]
str1000 -> 7 [ 11:21:30.02 - 11:21:29.95 ]
str5000 -> 13 [ 11:21:30.16 - 11:21:30.03 ]
-----------------
--- strlen2 ---
-----------------
str5 -> 1 [ 11:21:30.18 - 11:21:30.17 ]
str1 -> 1 [ 11:21:30.20 - 11:21:30.19 ]
str10 -> 0 [ 11:21:30.21 - 11:21:30.21 ]
str50 -> 1 [ 11:21:30.23 - 11:21:30.22 ]
str100 -> 1 [ 11:21:30.25 - 11:21:30.24 ]
str1000 -> 1 [ 11:21:30.27 - 11:21:30.26 ]
str5000 -> 1 [ 11:21:30.29 - 11:21:30.28 ]
-----------------
--- strlen3 ---
-----------------
str5 -> 6 [ 11:21:30.36 - 11:21:30.30 ]
str1 -> 6 [ 11:21:30.42 - 11:21:30.36 ]
str10 -> 4 [ 11:21:30.47 - 11:21:30.43 ]
str50 -> 5 [ 11:21:30.53 - 11:21:30.48 ]
str100 -> 5 [ 11:21:30.59 - 11:21:30.54 ]
str1000 -> 5 [ 11:21:30.65 - 11:21:30.60 ]
str5000 -> 6 [ 11:21:30.72 - 11:21:30.66 ]
-----------------
--- strlen0.3 ---
-----------------
str5 -> 1 [ 11:21:30.75 - 11:21:30.74 ]
str1 -> 1 [ 11:21:30.77 - 11:21:30.76 ]
str10 -> 1 [ 11:21:30.80 - 11:21:30.79 ]
str50 -> 0 [ 11:21:30.82 - 11:21:30.82 ]
str100 -> 1 [ 11:21:30.85 - 11:21:30.84 ]
str1000 -> 1 [ 11:21:30.87 - 11:21:30.86 ]
str5000 -> 1 [ 11:21:30.90 - 11:21:30.89 ]
-----------------
--- strlen0 ---
-----------------
str5 -> 1 [ 11:21:30.93 - 11:21:30.92 ]
str1 -> 1 [ 11:21:30.95 - 11:21:30.94 ]
str10 -> 1 [ 11:21:30.97 - 11:21:30.96 ]
str50 -> 1 [ 11:21:30.99 - 11:21:30.98 ]
str100 -> 0 [ 11:21:31.00 - 11:21:31.00 ]
str1000 -> 1 [ 11:21:31.02 - 11:21:31.01 ]
str5000 -> 0 [ 11:21:31.03 - 11:21:31.03 ]
# 25 Jan 2013 12:24
bluesxman
It's more meaningful to perform a large number iterations and time that. I found minor fluctuations in system load meant that some runs took 0.01s while others reported 0.05s with the exact same parameters.
Here's my own tunable (if not very successful at larger sizes) attempt and the harness I used to test it (calls a timing script I wrote a couple of years ago, included at the end):
Code: Select all
@echo off
setlocal enabledelayedexpansion
call timer.cmd stop >nul
set "string="
set "i=1000"
for %%S in (
0
5
326
8100
) do (
set "string="
echo:Begin
for /l %%C in (1,1,%%S) do set "string=!string!a"
echo !time! %%S
call timer.cmd start
for /l %%a in (1,1,%i%) do (
call :strLen "!string!" >nul
)
call timer.cmd stop
echo !time! %%S = !length!
echo:
)
pause
goto :EOF
:strLen
setlocal enabledelayedexpansion
set "blocksize=128"
set "string=%~1"
if not defined string (
set "length=0"
goto :end
)
set "upper="
set "lower="
for /l %%O in (0,%blocksize%,8192) do (
if not defined upper (
set "tempstring=!string:~%%O!"
if not defined tempstring (
set "upper=%%O"
) else (
set "lower=%%O"
)
)
)
set "length="
for /l %%O in (%lower%,1,%upper%) do (
if not defined length (
set "tempstring=!string:~%%O!"
if not defined tempstring (
set "length=%%O"
)
)
)
:end
endlocal & set "length=%length%"
timer.cmd wrote:
@echo off
setlocal
set time=%~2
set time=%time: =0%
set stamp.file=%temp%\%~n0.stamp
if /i "%~1" EQU "start" call :make.stamp
if /i "%~1" EQU "stop" call :read.stamp stop
if /i "%~1" EQU "lap" call :read.stamp lap
if "%~1" EQU "" call :status
endlocal
goto :EOF
:status
if exist "%stamp.file%" (
if /i "%~1" NEQ "/q" echo:Timer is active.
exit /b 0
)
echo:Timer is not active.
exit /b 1
:make.stamp
if exist "%stamp.file%" call :read.stamp stop
set start.time=%time%
(echo:%start.time%) > "%stamp.file%"
echo:Timer started %start.time%
goto :EOF
:read.stamp
call :status /q
if errorlevel 1 goto :EOF
set stop.time=%time%
set /p start.time=< "%stamp.file%"
echo:Timer started %start.time%
echo:Timer %1ped %stop.time%
if %1 EQU stop del "%stamp.file%"
call :calc.time.code %start.time%
set start.time.code=%errorlevel%
call :calc.time.code %stop.time%
set stop.time.code=%errorlevel%
set /a diff.time.code=stop.time.code - start.time.code
if %diff.time.code% LSS 0 set /a diff.time.code+=(24 * 60 * 60 * 100)
setlocal
set /a hs=diff.time.code %% 100
set /a diff.time.code/=100
set /a ss=diff.time.code %% 60
set /a diff.time.code/=60
set /a mm=diff.time.code %% 60
set /a diff.time.code/=60
set /a hh=diff.time.code
set hh=0%hh%
set mm=0%mm%
set ss=0%ss%
set hs=0%hs%
endlocal & set diff.time=%hh:~-2%:%mm:~-2%:%ss:~-2%.%hs:~-2%
echo %diff.time.code% hundredths of a second
echo %diff.time%
goto :EOF
:calc.time.code
setlocal
for /f "usebackq tokens=1,2,3,4 delims=:." %%a in ('%1') do (
set hh=%%a
set mm=%%b
set ss=%%c
set hs=%%d
)
set /a hh=((%hh:~0,1% * 10) + %hh:~1,1%) * 60 * 60 * 100
set /a mm=((%mm:~0,1% * 10) + %mm:~1,1%) * 60 * 100
set /a ss=((%ss:~0,1% * 10) + %ss:~1,1%) * 100
set /a hs=((%hs:~0,1% * 10) + %hs:~1,1%)
set /a time.code=hh + mm + ss + hs
endlocal & exit /b %time.code%
cmd | *sh | ruby | chef
----------------------------
# 04 Dec 2013 10:27
bearslumber
Hi Guys,
I have tried many of the functions but none of them handle spaces in the string correctly.
The string I am trying to parse is...
"C:\Program Files\Microsoft SQL Server\MSSQL11.SATLOC\MSSQL"
The functions return length of 56, but the length is 59.
I can only assume that the 3 spaces are not being counted.
Any ideas?
Bearslumber
----------------------------
# 04 Dec 2013 10:38
foxidrive
That string is 58 characters long, not counting the quotes.
![Smile :)](./images/smilies/icon_e_smile.gif)
Paste it into a file and look at the filesize.
----------------------------
# 04 Dec 2013 12:03
bearslumber
@foxdrive
Yes. You are correct.
either way though, none of the functions I tried returned 58. They all return 56.
I also tried the strLen function in DosTips, and that also returns 56.
there is something in common with all the functions. Either that or I am definitely going mad.
Any ideas?
Any help is greatly appreciated.
Bearslumber
----------------------------
#04 Dec 2013 12:12
npocmaka
all of the functions dequote the passed string.So this reduce the length to 56.
----------------------------
# 04 Dec 2013 12:44
foxidrive
No npocmaka, the quoted string is 60 characters.
But to Bearslumber, I tried the first routine in this thread and it returns 58 here (with a space)
Code: Select all
@echo off
set "abc=C:\Program Files\Microsoft SQL Server\MSSQL11.SATLOC\MSSQL"
call :strlen "%abc%" def
echo "%def%"
pause
goto :EOF
:strlen [%1 -string, %2 variable to store result in]
setlocal
set string=%~1
rem if the string contains double quotes it will harm the IF checks
set string=%string:"=.%
set var=%~2
set /a counter=0
if "%string%" equ "" goto :return
:loop
rem counter will hold the 1/2 of the string lenght
set /a counter=%counter%+1
set string=%string:~1,-1%
if "%string%" equ "" (
goto :endloop
)
goto :loop
:endloop
set string=%~1
set string=%string:"=.%
set /a counter=2*%counter%-1
rem checking if the string is with even or with uneven lenght
call set string=%%string:~%counter%%%
if "%string%" neq "" set /a counter=%counter%+1
echo %counter%
:return
endlocal & set %var%=%counter% >nul 2>&1
----------------------------
# 04 Dec 2013 12:54
npocmaka
Code: Select all
@echo off
set "str="C:\Program Files\Microsoft SQL Server\MSSQL11.SATLOC\MSSQL""
call :strlen0.3 str
call :strlen2.5 str
call :strlen4 str
call :strlen0 str
goto :eof
:strlen0.3 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "s=#!%~1!"
set "len=0"
for %%A in (2187 729 243 81 27 9 3 1) do (
set /A mod=2*%%A
for %%Z in (!mod!) do (
if "!s:~%%Z,1!" neq "" (
set /a "len+=%%Z"
set "s=!s:~%%Z!"
) else (
if "!s:~%%A,1!" neq "" (
set /a "len+=%%A"
set "s=!s:~%%A!"
)
)
)
)
endlocal & if "%~2" neq "" (set %~2=%len%) else echo **%len%**
exit /b
:strlen2.5 StrVar [RtnVar]
setlocal enableDelayedExpansion
set "s=!%~1!"
set len=0
if defined s for /l %%N in (1,1,8192) do if "!s:~%%N,-%%N!" equ "" (
set len=%%N
goto :break
)
:break
set /a len=2*!len!-1
for %%E in (!len!) do (
set s=!s:~%%E!
)
if "!s!" neq "" set /a len=!len!+1
endlocal & if "%~2" neq "" (set %~2=%len%) else echo ~~%len%~~
exit /b
:strlen4 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "tempFile=%temp%\strlen%random%.tmp"
echo(!%~1!>"%tempFile%"
for %%F in ("%tempFile%") do set /a len=%%~zF-2
del "%tempFile%"
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
:strlen0 StrVar [RtnVar]
setlocal EnableDelayedExpansion
set "s=#!%~1!"
set "len=0"
for %%N in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%N,1!" neq "" (
set /a "len+=%%N"
set "s=!s:~%%N!"
)
)
endlocal&if "%~2" neq "" (set %~2=%len%) else echo %len%
exit /b
Code: Select all
**60**
~~60~~
60
60
Code: Select all
**58**
~~58~~
58
58
# 04 Dec 2013 21:35
bearslumber
Hi Guys,
Many thanks for your answers.
I attach a code snippet to give you an idea of what I'm trying to do (the context) and under which I get the result of 56. I used npocmaka's strlen0.3.
I'm not sure how to apply the quotes in this situation as implied by the posts, and any help is greatly appreciated.
Code: Select all
echo off
setlocal ENABLEEXTENSIONS
set /A LINES_TO_SKIP=4
rem on 64 bit architecture
wmic os get osarchitecture | findstr /I 64 && set /A LINES_TO_SKIP=2
set KEY_NAME="HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\SATLOC\Setup"
set VALUE_NAME="SQLPath"
FOR /F "usebackq skip=%LINES_TO_SKIP% tokens=2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
set INSTANCE_PATHTYPE=%%A
set INSTANCE_PATHVALUE=%%B
)
echo on
echo %INSTANCE_PATHVALUE%
set len=
call:strlen0.3 "%INSTANCE_PATHVALUE%" len
echo %len%
C:\Program Files\Microsoft SQL Server\MSSQL11.SATLOC\MSSQL
56
----------------------------
# 04 Dec 2013 23:53
Simon Sheppard
I'm thinking if you are going to create a temp file, then why not just let the file system calculate the size/length for you, just subtract 2 to account for the terminating CR/LF
Code: Select all
@echo off
setlocal
Set _temp=%temp%\strlen%random%.txt
Echo %1>%_temp%
For /f "tokens=3" %%G in ('dir /-C %_temp% ^| find "File(s)"') Do Set /a _len=%%G-2
Echo %_len%
Del %_temp%
strlen.cmd "some string to measure"
----------------------------
# 05 Dec 2013 10:06
bearslumber
Thank you Simon,
Your solution does resolve the issue, albeit a deviation.
With respect to the strlen functions, I believe that the functions would be most useful when used in context where a variable is extrapolated in line from another source, for example in my demonstration.
From my naive perspective, it would seem that hard-coding the string and testing the function provides the expected results, where as if I "stringify" (for want of a better word) a variable and use it in context we get a different result, as I believe I have demonstrated.
This to me suggests there is something subtly different in the formatting of the hard-coded string and my "stringification" of the variable.
I would be grateful if someone could point out that subtlety, or point me to some documentation that would explain the difference.
Don't get me wrong because I am impressed with the strlen solutions, and I still believe the strlen functions are the best common solutions to add to a library. I just need to understand how to use the function it in context, and correctly.
Thanks again.
Bearslumber
----------------------------
# 05 Dec 2013 12:07
foxidrive
There are two ways to quote a variable, when you need to protect characters like & for example, or prevent trailing spaces.
This will work and leave the variable contents without surrounding quotes
set "variable=one & two"
This will work and leave the variable contents with surrounding quotes
set variable="one & two"
This will fail when just setting the variable, as is why the above need to be used:
set variable=one & two
It would seem that the way you passed the variable was fubar - but it's hard to see how it would report *less* characters than there are.
----------------------------
# 05 Dec 2013 13:04
bearslumber
Thanks for enlightening me foxdrive, much appreciated.
I get it now. The key to the issue is the fubar! with an emphasis on the "FU"!!!
I needed to pass the variable without the parenthesis.
i.e.
call:strlen0.3 INSTANCE_PATHVALUE len
and not
call:strlen0.3 %INSTANCE_PATHVALUE% len
Doh!!!
----------------------------
# 17 Jun 2016 18:12
Simon Sheppard
Over on the main site I have added this page
https://ss64.com/nt/syntax-strlen.html
This example is intended to be simple and easy to follow rather than particularly high performance with long strings. It is probably most similar to StrLen2 that Dave Benham posted above.
Theres also a link back to this thread.
----------------------------
# 20 Jun 2016 07:57
jeb
Hi Simon,
nice idea to add the strlen function to the site. smile
I can understand that you take a simple implementation, but ....
Your version fails in too many situaltions.
All special characters breaks your code.
strlen.cmd "Cat&Dog"
strlen.cmd 1^>2
And I can't see why you should use any replace operations.
I would change the code to
Code: Select all
Echo off
Setlocal EnableDelayedExpansion
Set "_str=%*"
:: Test if empty
if not defined _str Echo String Length = 0 & ENDLOCAL & set _strlen=0&goto:eof
For /l %%g in (0,1,8191) do (
REM extract one character
Set "_char=!_str:~%%g,1!"
REM if _char is empty we are at the end of the string
if not defined _char Echo String Length = %%g & ENDLOCAL & set _strlen=%%g& goto:eof
)
----------------------------
# 29 Jun 2016 17:48
Simon Sheppard
^ Thanks Jeb that example is better, interestingly your version is similar to the strlen function in the Batchography book which I have just been reading.
I left in the removal of quotes just so that the script/function can be called passing a string with or without surrounding quotes. Also I mentioned the special characters and linked back to this thread.
Edit: after playing around with this some more, I switched the web page to use strLen7 as it is quite a bit faster.