Split, indexof, lastindexof, reverse, endsWith, startsWith (in progress)

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

Split, indexof, lastindexof, reverse, endsWith, startsWith (in progress)

Post by MigrationUser »

12 Apr 2013 21:48
npocmaka


Latest versions:
SPLIT

Code: Select all

:split [%1 - string to be splitted;%2 - split by;%3 - possition to get; %4 - if defined will store the result in variable with same name]

setlocal EnableDelayedExpansion

set "string=%~2%~1"
set "splitter=%~2"
set /a position=%~3

set LF=^


rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
	for /f "delims=" %%R in ("!splitter!") do ( 
		set "var=!string:%%~R%%~R=%%~L!"
		set "var=!var:%%~R=%%~L!"
		if "!var!" EQU "!string!" (
		 	echo "%~1" does not contain "!splitter!" >&2
		 	exit /B 1
		)
	)
)

if "!var!" equ "" (
	endlocal & if "%~4" NEQ "" ( set "%~4=")
)
if !position! LEQ 0 ( set "_skip=" ) else (set  "_skip=skip=%position%")

for /f  "eol= %_skip% delims=" %%P in (""!var!"") DO (
	
	if "%%~P" neq "" ( 
		set "part=%%~P" 
		goto :end_for 
	)
)
set "part="
:end_for
if not defined part (
 endlocal
 echo Index Out Of Bound >&2 
 exit /B 2
)
endlocal & if "%~4" NEQ "" (set %~4=%part%) else echo %part%
exit /b 0
indexof

Code: Select all

:indexof [%1 - string ; %2 - find index of ; %3 - if defined will store the result in variable with same name]
@echo off
setlocal enableDelayedExpansion

set "str=%~1"
set "s=!str:%~2=&rem.!"
set s=#%s%
if "%s%" equ "#%~1" endlocal& if "%~3" neq "" (set %~3=-1&exit /b 0) else (echo -1&exit /b 0) 

  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 "%~3" neq "" (set %~3=%len%) else echo %len%
exit /b 0
last index of (needs fixes)

Code: Select all

:lastindexof [%1 - string ; %2 - find last index of ; %3 - if defined will store the result in variable with same name]
@echo off
setlocal enableDelayedExpansion 


set "str=%~1"
set "splitter=%~2"

set LF=^


rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
	for /f "delims=" %%R in ("!splitter!") do ( 
		set "var=!str:%%R=%%L!"
	)
)

for /f  delims^=^" %%P in ("!var!") DO ( 
	set "last_part=%%~P"  
)

if "!last_part!" equ ""  if "%~3" NEQ "" (
 echo "not contained" >2 
 endlocal
 set %~3=-1 
 exit
) else (
 echo "not contained" >2 
 endlocal
 echo -1 
)
setlocal DisableDelayedExpansion

set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
set $strLen=for /L %%n in (1 1 2) do if %%n==2 (%\n%
      for /F "tokens=1,2 delims=, " %%1 in ("!argv!") do (%\n%
         set "str=A!%%~2!"%\n%
           set "len=0"%\n%
           for /l %%A in (12,-1,0) do (%\n%
             set /a "len|=1<<%%A"%\n%
             for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
           )%\n%
           for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~1=%%v") else echo %%v%\n%
      ) %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,


%$strlen% strlen,str
%$strlen% plen,last_part
%$strlen% slen,splitter

set /a lio=strlen-plen-slen
endlocal & if "%~3" NEQ "" (set %~3=%lio%) else echo %lio%
exit /b 0
Reverse:

Code: Select all

:reverse [%1 - string to reverse ; %2 - if defined will store the result in variable with same name]
@echo off
setlocal disableDelayedExpansion
set "str=%~1"
set LF=^


rem ** Two empty lines are required
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
set $strLen=for /L %%n in (1 1 2) do if %%n==2 (%\n%
      for /F "tokens=1,2 delims=, " %%1 in ("!argv!") do (%\n%
         set "str=A!%%~2!"%\n%
           set "len=0"%\n%
           for /l %%A in (12,-1,0) do (%\n%
             set /a "len|=1<<%%A"%\n%
             for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
           )%\n%
           for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~1=%%v") else echo %%v%\n%
      ) %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,

%$strLen% len,str
setlocal enableDelayedExpansion

set /a half=len/2
set "res=!str:~%half%,-%half%!"

for /L %%C in (%half%,-1,0) do (
	set /a len=%len%-1-%%C
	if %%C neq %half% (
		for  %%c in (!len!) do (			
			set "res=!str:~%%c,1!!res!!str:~%%C,1!"
		)
	)

)
endlocal & endlocal & if "%~2" NEQ "" (set %~2=%res%) else echo %res%
startsWith:

Code: Select all

:startsWith [%1 - string to be checked;%2 - string for checking ] 
@echo off
rem :: sets errorlevel to 1 if %1 starts with %2 else sets errorlevel to 0

setlocal EnableDelayedExpansion

set "string=%~1"
set "checker=%~2"
rem set "var=!string:%~2=&echo.!"
set LF=^


rem ** Two empty lines are required
rem echo off
for %%L in ("!LF!") DO (
 	for /f "delims=" %%R in ("!checker!") do ( 
 		rem set "var=!string:%%~R%%~R=%%~L!"
 		set "var=!string:%%~R=#%%L!"
 	)
)
for /f "delims=" %%P in (""!var!"") DO (
	if "%%~P" EQU "#" goto :yes
	goto :no
)
:yes
endlocal & verify set_error 2>nul
goto :eof
:no
endlocal & ( echo | shift )
goto :eof
ends With

Code: Select all

:endsWith [%1 - string to be checked;%2 - string for checking ] 
@echo off
rem :: sets errorlevel to 1 if %1 ends with %2 else sets errorlevel to 0

setlocal EnableDelayedExpansion

set "string=%~1"
set "checker=%~2"
rem set "var=!string:%~2=&echo.!"
set LF=^


rem ** Two empty lines are required
rem echo off
for %%L in ("!LF!") DO (
 	for /f "delims=" %%R in ("!checker!") do ( 
 		rem set "var=!string:%%~R%%~R=%%~L!"
 		set "var=!string:%%~R=%%L#!"
 	)
)
for /f "delims=" %%P in (""!var!"") DO (
	set "temp=%%~P"
)
if "%temp%" EQU "#" goto :yes
goto :no
:yes
endlocal & verify set_error 2>nul
goto :eof
:no
endlocal & ( echo | shift )
goto :eof

Code: Select all

@echo off
:split
setlocal 
set string=%~1
set splitter=%~2
set /a position=%~3

setlocal enabledelayedexpansion
rem /--------------
rem  Here I'm searching for
rem  a suitable specaial character
rem to replace the strinq for splitting 
rem with and to use it for processing
rem with FOR /F command
rem ---------------/
for %%C in (¬ ¦ ¶ § ± ° • » « µ г м ж ¦ з - - Х ¬ л ¦ у ) do (
	set temp_string=!string:%%C=!
	
	rem   if the current special symbol is not part of the splitted string we can use it
	if "!temp_string!" EQU "!string!" (
		for /f "delims=" %%R in ("!splitter!") do ( 
			 set temp_string=!string:%%R=%%C!
		)
		set "char=%%C"
		goto :parametrize_for
	)
)

	echo You are passing binary string , arent you? Cannot help you.
	endlocal
	exit /B 1
	
:parametrize_for
endlocal & set char=%char% & set temp_string=%temp_string%
rem creating the for /f with needed parameters
set "p_for=for /f "tokens=%position% delims=%char%" %%S in ("%temp_string%")"
%p_for% do (
	echo %%S
)
goto :eof


I still have no enough courage to try with a string that contains quotes...

The idea is to substitute the string we need to split by , with a single special symbol (which is not part of the string) and then to use FOR /F
(I'm not sure how this line will be displayed (¬ ¦ ¶ § ± ° • » « µ г м ж ¦ з - - Х ¬ л ¦ у ) - it depends on localization , but it should be possible to be copied)

Another thing is the parametrization of the last FOR - I'm using a variable passed directly as a command .Unfortunately this does not work with delayed expansion.

Also I've tried to avoid using of CALL - may be the performance is better that way...

Last edited by npocmaka (13 Sep 2013 11:45)

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

#15 Apr 2013 09:35
jeb


There exists another way to split at strings.

Replacing the string-splitter with a LF.

Code: Select all

@echo off
@echo off
setlocal DisableDelayedExpansion
set "string=%~1"
set "splitter=%~2"

setlocal EnableDelayedExpansion
set LF=^


rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
	for /f "delims=" %%R in ("!splitter!") do ( 
		set "var=!string:%%R=%%L!"		
	)
)
for /f "delims=" %%P in (""!var!"") DO echo '%%~P'
Tested with

Code: Select all

test #Split$Hello#Split$Hallo#Split$ #Split$
Output

Code: Select all

''
'Hello'
'Hallo'
''
jeb

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

#15 Apr 2013 23:48
npocmaka

Ha! That was clever :-) - as there's no way to pass LF as a part of command line parameter, and harder in a script.

As I still want to grab only specified part of the string I've added some things to your code:

Code: Select all

@echo off
setlocal DisableDelayedExpansion
set "string=%~1"
set "splitter=%~2"
set /a position=%~3

setlocal EnableDelayedExpansion
set LF=^


rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
	for /f "delims=" %%R in ("!splitter!") do ( 
		set "var=!string:%%R=%%L!"
		echo !var!
		rem if "!var!" EQU "!string!" (
		rem 	echo "!string!" does not contain "!splitter!"
		rem 	exit /B 1
		rem )
	)
)
set /a counter=0
for /f delims^=^" %%P in ("!var!") DO ( 
	set /a counter=!counter!+1
	if !counter! EQU !position! ( 
		echo %%~P
		endlocal
		exit /B 0
	)
)
echo Index Out Of Bound
endlocal
exit /B 2
(none of the snippets work if the passed string contains ! or " ...)

Last edited by npocmaka (15 Apr 2013 23:54)

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

#24 Jun 2013 13:59
jeb
npocmaka wrote:

Ha! That was clever :-) - as there's no way to pass LF as a part of command line parameter, and harder in a script.
LF can be part of a command line parameter.

Code: Select all

c:\temp> myBatch.bat Hello^

world
But it's (nearly) impossible to get the parameter in a secure way.

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

#24 Jun 2013 19:38
Aacini
jeb wrote:

LF can be part of a command line parameter.

c:\temp> myBatch.bat Hello^

world

But it's (nearly) impossible to get the parameter in a secure way.
This is another example of the usefulness of !1, !2 ... (if such a thing would be possible), besides to take parameters inside a block after SHIFT: ( shift & echo !1 )

Is there a way to propose this modification to Microsoft? smile

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

#08 Sep 2013 12:51
npocmaka

Just saw this post by jeb:

And this is the first time I ever see this: set "part=%%str:%~3=&rem.%%"
Which helps to create a much shorter version of the splitting script:

Code: Select all

:splitter [%1 - string to be splitted;%2 - split by;%3 - possition to get]
@echo off
setlocal enableDelayedExpansion 
set "string=%~1"
set /a position=%~3-1
::prepeare for
set "_for=for /f "skip=%position% delims=" %%S in"
set "p=!string:%~2=&echo.!"

%_for%  ('echo !p!') do (
	echo %%S
	goto :endfor
)
:endfor
endlocal

And this is just another way to set a new line as a variable (quotes are critical on the second line.And works only with set .For instance echo "%nl:_=&echo(%" won't work ):

Code: Select all

C:\>set nl=_

C:\>set "nl=%nl:_=&echo(%"

C:\>echo ~%nl%~
~
~

And a way to set a command result to variable without for /f

Code: Select all

set command_result=_
set "command_result=%command_result:_=&dir%"
echo %comand_result%

Probably this is well known but was new for me :-)

Last edited by npocmaka (08 Sep 2013 12:55)

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

#08 Sep 2013 15:35
foxidrive
npocmaka wrote:

And a way to set a command result to variable without for /f

Code: Select all

    set command_result=_
    set "command_result=%command_result:_=&dir%"
    echo %command_result%
You had left an m out of the last line - but this sets a variable to &dir and executes the command.
It doesn't set the result of the command into a variable.

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

#08 Sep 2013 15:41
dbenham


I was just going to point out the same.

The NL variable suffers the same problem.

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

#08 Sep 2013 19:40
npocmaka

Aaahh. That makes more sense.Nevertheless it's a great trick.

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

#08 Sep 2013 22:07
npocmaka

I liked the jeb's indexof so I decided to improve it a little bit (there's a built-in strlen to avoid CALLs).Plus I one last index of subroutine (with strlen macro stolen from dostips again from jeb - for this I need three calculations of string length so the macro is need for better performance) and small improvements in split.

Code: Select all

@echo off
:split [%1 - string to be splitted;%2 - split by;%3 - possition to get; %4 - if defined will store the result in variable with same name]

setlocal EnableDelayedExpansion

set "string=%~1"
set "splitter=%~2"
set /a position=%~3

set LF=^


rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
	for /f "delims=" %%R in ("!splitter!") do ( 
		set "var=!string:%%R=%%L!"
		if "!var!" EQU "!string!" (
		 	echo "!string!" does not contain "!splitter!" >2
		 	exit /B 1
		)
	)
)

if !position! equ 0 ( set "_skip=" ) else (set  "_skip=skip^=%position%^")
for /f  %_skip% delims^=^" %%P in ("!var!") DO ( 
	set "part=%%~P"
	goto :end_for
)
:end_for
if "!part!" equ ""  echo Index Out Of Bound >2 &exit /B 2
endlocal & if "%~4" NEQ "" (set %~4=%part%) else echo %part%
exit /b 0

Code: Select all

:indexof [%1 - string ; %2 - find index of ; %3 - if defined will store the result in variable with same name]
@echo off
setlocal enableDelayedExpansion

set "str=%~1"
set "s=!str:%~2=&rem.!"
set s=#%s%
if "%s%" equ "#%~1" endlocal& if "%~3" neq "" (set %~3=-1&exit /b 0) else (echo -1&exit /b 0) 

  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 "%~3" neq "" (set %~3=%len%) else echo %len%
exit /b 0

Code: Select all

:lastindexof [%1 - string ; %2 - find last index of ; %3 - if defined will store the result in variable with same name]
@echo off
setlocal enableDelayedExpansion 


set "str=%~1"
set "p=!str:%~2=&echo.!"
set "splitter=%~2"

set LF=^


rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
	for /f "delims=" %%R in ("!splitter!") do ( 
		set "var=!str:%%R=%%L!"
	)
)

for /f  delims^=^" %%P in ("!var!") DO ( 
	set "last_part=%%~P"  
)

if "!last_part!" equ ""  if "%~3" NEQ "" (
 echo "not contained" >2 
 endlocal
 set %~3=-1 
 exit
) else (
 echo "not contained" >2 
 endlocal
 echo -1 
)
setlocal DisableDelayedExpansion

set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set argv=original
set $strLen=for /L %%n in (1 1 2) do if %%n==2 (%\n%
      for /F "tokens=1,2 delims=, " %%1 in ("!argv!") do (%\n%
         set "str=A!%%~2!"%\n%
           set "len=0"%\n%
           for /l %%A in (12,-1,0) do (%\n%
             set /a "len|=1<<%%A"%\n%
             for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
           )%\n%
           for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~1=%%v") else echo %%v%\n%
      ) %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,


%$strlen% strlen,str
%$strlen% plen,last_part
%$strlen% slen,splitter

set /a lio=strlen-plen-slen
endlocal & if "%~3" NEQ "" (set %~3=%lio%) else echo %lio%
exit /b 0
----------------------------

#11 Sep 2013 21:14
npocmaka

The split was not pretty accurate in the last version- if I have two new lines after the substitution I'll be not able to skip to the right line in the last FOR loop.And I'm not sure if the string starts with the splitting string or not.So here's the fix:

Code: Select all

:split [%1 - string to be splitted;%2 - split by;%3 - possition to get; %4 - if defined will store the result in variable with same name]

setlocal EnableDelayedExpansion

set "string=%~2%~1"
set "splitter=%~2"
set /a position=%~3

set LF=^


rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
	for /f "delims=" %%R in ("!splitter!") do ( 
		set "var=!string:%%~R%%~R=%%~L!"
		set "var=!var:%%~R=%%~L!"
		if "!var!" EQU "!string!" (
		 	echo "%~1" does not contain "!splitter!" >&2
		 	exit /B 1
		)
	)
)

if "!var!" equ "" (
	endlocal & if "%~4" NEQ "" ( set "%~4=")
)
if !position! LEQ 0 ( set "_skip=" ) else (set  "_skip=skip=%position%")

for /f  "eol= %_skip% delims=" %%P in (""!var!"") DO (
	
	if "%%~P" neq "" ( 
		set "part=%%~P" 
		goto :end_for 
	)
)
set "part="
:end_for
if not defined part (
 endlocal
 echo Index Out Of Bound >&2 
 exit /B 2
)
endlocal & if "%~4" NEQ "" (set %~4=%part%) else echo %part%
exit /b 0
There two important things - I explicitly add a splitting string at the beginning and I've set additional substitution for for doubled occurrence of the splitting string.Now it works much better.But still didn't wind a good way to dealt with the special characters.I'll need some mad disable/enable delayed expansion sequences.

(And will keep the latest versions of these string functions on the first post)

Last edited by npocmaka (12 Sep 2013 09:15)

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

#12 Sep 2013 10:04
npocmaka

and one :reverse subroutine (again cannot deal with special characters):

Code: Select all

:reverse [%1 - string to reverse ; %2 - if defined will store the result in variable with same name]
@echo off
setlocal disableDelayedExpansion
set "str=%~1"
set LF=^


rem ** Two empty lines are required
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
set $strLen=for /L %%n in (1 1 2) do if %%n==2 (%\n%
      for /F "tokens=1,2 delims=, " %%1 in ("!argv!") do (%\n%
         set "str=A!%%~2!"%\n%
           set "len=0"%\n%
           for /l %%A in (12,-1,0) do (%\n%
             set /a "len|=1<<%%A"%\n%
             for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
           )%\n%
           for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~1=%%v") else echo %%v%\n%
      ) %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,

%$strLen% len,str
setlocal enableDelayedExpansion

set /a half=len/2
set "res=!str:~%half%,-%half%!"

for /L %%C in (%half%,-1,0) do (
	set /a len=%len%-1-%%C
	if %%C neq %half% (
		for  %%c in (!len!) do (			
			set "res=!str:~%%c,1!!res!!str:~%%C,1!"
		)
	)

)
endlocal & endlocal & if "%~2" NEQ "" (set %~2=%res%) else echo %res%
----------------------------

#13 Sep 2013 11:47
npocmaka


and startsWith and endsWith functions:

startsWith:

Code: Select all

:startsWith [%1 - string to be checked;%2 - string for checking ] 
@echo off
rem :: sets errorlevel to 1 if %1 starts with %2 else sets errorlevel to 0

setlocal EnableDelayedExpansion

set "string=%~1"
set "checker=%~2"
rem set "var=!string:%~2=&echo.!"
set LF=^


rem ** Two empty lines are required
rem echo off
for %%L in ("!LF!") DO (
 	for /f "delims=" %%R in ("!checker!") do ( 
 		rem set "var=!string:%%~R%%~R=%%~L!"
 		set "var=!string:%%~R=#%%L!"
 	)
)
for /f "delims=" %%P in (""!var!"") DO (
	if "%%~P" EQU "#" goto :yes
	goto :no
)
:yes
endlocal & verify set_error 2>nul
goto :eof
:no
endlocal & ( echo | shift )
goto :eof
ends With

Code: Select all

:endsWith [%1 - string to be checked;%2 - string for checking ] 
@echo off
rem :: sets errorlevel to 1 if %1 ends with %2 else sets errorlevel to 0

setlocal EnableDelayedExpansion

set "string=%~1"
set "checker=%~2"
rem set "var=!string:%~2=&echo.!"
set LF=^


rem ** Two empty lines are required
rem echo off
for %%L in ("!LF!") DO (
 	for /f "delims=" %%R in ("!checker!") do ( 
 		rem set "var=!string:%%~R%%~R=%%~L!"
 		set "var=!string:%%~R=%%L#!"
 	)
)
for /f "delims=" %%P in (""!var!"") DO (
	set "temp=%%~P"
)
if "%temp%" EQU "#" goto :yes
goto :no
:yes
endlocal & verify set_error 2>nul
goto :eof
:no
endlocal & ( echo | shift )
goto :eof
Now I'm going to see how to make all these functions more special-symbol-proof.
Post Reply