stringlength.cmd - just created - for anybody who can use it

Microsoft Windows
Post Reply
User avatar
MigrationUser
Posts: 336
Joined: 2021-Jul-12, 1:37 pm
Contact:

stringlength.cmd - just created - for anybody who can use it

Post by MigrationUser »

20 Jul 2008 17:06
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
I'm also trying to "eat" the string from two directions with a tiny hope that this will be faster for a long strings.

----------------------------

#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
length sec*1000
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
length sec*1000
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
length sec*1000
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
length sec*1000
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
length sec*1000
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
length sec*1000
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
length sec*1000
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
Last edited by npocmaka (22 Jan 2013 00:11)

----------------------------

# 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!"
            )
        )
    )
  )
Have no idea how fast this is :-)
(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
It was a little bit harder for implementation than I thought before start writing it.
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%
Last edited by bluesxman (25 Jan 2013 12:27)

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. :)
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
Last edited by foxidrive (04 Dec 2013 12:52)

----------------------------
# 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
output

Code: Select all

    **60**
    ~~60~~
    60
    60
with removed quotes:

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%
Output:

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%
Call it as
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
 )
Last edited by jeb (20 Jun 2016 07:58)

----------------------------
# 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.
PiotrMP006
Posts: 19
Joined: 2021-Sep-01, 10:57 am

Re: stringlength.cmd - just created - for anybody who can use it

Post by PiotrMP006 »

Hi

Why is there a "#" sign at the beginning of the code set "s=#!%~1!"?

Code: Select all

goto:eof
:strlen  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
User avatar
Simon Sheppard
Posts: 190
Joined: 2021-Jul-10, 7:46 pm
Contact:

Re: stringlength.cmd - just created - for anybody who can use it

Post by Simon Sheppard »

I think the hash sign is there to cover the case where an empty string is passed.
Otherwise set "s=" would remove the variable and cause errors.

The indexing of a variable starts at 0 and the smallest number in the sequence (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) is 1, so character 0 is never counted.
https://ss64.com/nt/syntax-substring.html
Post Reply