15

I want to split a string in two parts, without using any for loop.

For example, I have the string in a variable:

str=45:abc

I want to get 45 in a variable and abc in another variable. Is it possible in batch file?

pattern is like somenumber:somestring

CrazyCoder
  • 622
  • 5
  • 10
  • 27

6 Answers6

18

You could split the str with different ways.

The for loop, you don't want use it.

The trailing part is easy with the * (match anything until ...)
set "var2=%str:*:=%"

The leading part can be done with a nasty trick
set "var1=%str::="^&REM #%

The caret is needed to escape the ampersand,
so effectivly the colon will be replaced by "&REM # So in your case you got the line after replacing
set "var1=4567"&REM #abcde
And this is splitted into two commands

set "var1=4567"
REM #abcde` 

And the complete code is here:

set "str=4567:abcde"
echo %str%
set "var1=%str::="^&REM #%
set "var2=%str:*:=%"
echo var1=%var1% var2=%var2%

Edit 2: More stable leading part

Thanks Dave for the idea to use a linefeed.
The REM technic isn't very stable against content with quotes and special characters.
But with a linefeed trick there exists a more stable version which also works when the split argument is longer than a single character.

@echo off
setlocal enableDelayedExpansion
set ^"str=456789#$#abc"
for /F "delims=" %%a in (^"!str:#$#^=^

!^") do (
  set "lead=%%a"
  goto :break
)
:break
echo !lead!

Solution 3: Adpated dbenhams answer

Dbenham uses in his solution a linefeed with a pipe.
This seems a bit over complicated.
As the solution uses the fact, that the parser removes the rest of the line after an unescaped linefeed (when this is found before or in the special character phase).

At first the colon character is replaced to a linefeed with delayed expansion replacement.
That is allowed and the linefeed is now part of the variable.
Then the line set lead=%lead% strips the trailing part.
It's better not to use the extended syntax here, as set "lead=%lead%" would break if a quote is part of the string.

setlocal enableDelayedExpansion
set "str=45:abc"
set ^"lead=!str::=^

!"
set lead=%lead%
echo "!lead!"
Community
  • 1
  • 1
jeb
  • 70,992
  • 15
  • 159
  • 202
  • ^&REM # ... could you please explain it. – CrazyCoder Jan 31 '13 at 09:08
  • +1 This is awesome jeb, much better than the version I came up with (I didn't bother posted after seeing yours :) ) This also works with just `^&REM` - without the hash. – Bali C Jan 31 '13 at 11:38
  • @BaliC The hash is for some nasty contents like `/?` – jeb Jun 12 '13 at 08:16
  • Regarding Edit 2 - I thought the whole point was to avoid use of FOR ;-) – dbenham Jun 14 '13 at 10:47
  • @dbenham I know :-) but the point is, that this seems to be the only safe way. But I append a simplified version of your solution without a pipe – jeb Jun 14 '13 at 12:01
  • I've added a safe version to my answer that avoids FOR – dbenham Jun 14 '13 at 12:09
  • Ooh, nice simplification of my first solution. – dbenham Jun 14 '13 at 12:13
  • If you are having trouble understanding how `set "var1=%str::="^&REM #%` becomes `set "var1=4567"&REM #abcde` in the hack, remember [expansion of `%...%` happens once before the line is executed](https://ss64.com/nt/delayedexpansion.html). So when looking at `set "var1=%str::="^&REM #%`, replace the `%...%` first BEFORE doing the assignment. `:` gets replaced by `="&REM #` (^ is just escape so ^& becomes &) and `%..%` becomes `4567:abcde` -> `4567&REM #abcde`. Now place that in the assignment `set "var1=%...%` -> `set "var1=4567^&REM #abcde`. It's like injecting code into the current line. – dosentmatter Feb 27 '21 at 18:10
4

You can try this . If its fixed that numbers to left of the colon will be always 2 & to the right will be 3. Then following code should work assuming your str has the value.

set "str=45:abc"
echo %str%
set var1=%str:~0,2%
set var2=%str:~3,3%
echo %var1% %var2%

Keep me posted. :)

3

It seems pointless to avoid using a FOR loop, but it does make the problem interesting.

As jeb has pointed out, getting the trailing part is easy using !str:*:=!.

The tricky bit is the leading part. Here is an alternative to jeb's solution.

You can insert a linefeed into a variable in place of the : using the following syntax

setlocal enableDelayedExpansion
set "str=45:abc"
echo !str::=^

!

--OUTPUT--

45
abc

The empty line above the last ! is critical.

I'm not sure why, but when the output of the above is piped to a command, only the first line is preserved. So the output can be piped to a FINDSTR that matches any line, and that result directed to a file that can then be read into a variable using SET /P.

The 2nd line must be eliminated prior to using SET /P because SET /P does not recognize <LF> as a line terminator - it only recognizes <CR><LF>.

Here is a complete solution:

@echo off
setlocal enableDelayedExpansion
set "str=45:abc"
echo(!str::=^

!|findstr "^" >test.tmp
<test.tmp set /p "var1="
del test.tmp
set "var2=!str:*:=!"
echo var1=!var1!  var2=!var2!

Update

I believe I've mostly figured out why the 2nd line is stripped from the output :)

It has to do with how pipes are handled by Windows cmd.exe with each side being processed by a new CMD.EXE thread. See Why does delayed expansion fail when inside a piped block of code? for a related question with a great answer from jeb.

Just looking at the left side of the piped command, I believe it is parsed (in memory) into a statement that looks like

C:\Windows\system32\cmd.exe /S /D /c" echo {delayedExpansionExpression}"

I use {delayedExpansionExpression} to represent the multi-line search and replace expansion that has not yet occurred.

Next, I think the variable expression is actually expanded and the line is broken in two by the search and replace:

C:\Windows\system32\cmd.exe /S /D /c" echo 43
abc"

Only then is the command executed, and by normal cmd.exe rules, the command ends at the linefeed. The quoted command string is missing the end quote, but the parser doesn't care about that.

The part I am still puzzled by is what happens to the abc"? I would have thought that an attempt would be made to execute it, resulting in an error message like 'abc"' is not recognized as an internal or external command, operable program or batch file. But instead it appears to simply get lost in the ether.

note - jeb's 3rd comment explains why :)


Safe version without FOR

My original solution will not work with a string like this & that:cats & dogs. Here is a variation without FOR that should work with nearly any string, except for string length limits and trailing control chars will be stripped from leading part.

@echo off
setlocal enableDelayedExpansion
set "str=this & that:cats & dogs"
set ^"str2=!str::=^

!^"
cmd /v:on /c echo ^^!str2^^!|findstr /v "$" >test.tmp
<test.tmp set /p "var1="
del test.tmp
set "var2=!str:*:=!"
echo var1=!var1!  var2=!var2!

I delay the expansion until the new CMD thread, and I use a quirk of FINDSTR regex that $ only matches lines that end with <cr>. The first line doesn't have it and the second does. The /v option inverts the result.

Community
  • 1
  • 1
dbenham
  • 119,153
  • 25
  • 226
  • 353
  • +1: Good idea to use a linefeed here, but I suppose your analysis is wrong. The `{delayedExpansionExpression}` has already occoured when the `cmd /S/D/c` is started` as in the new cmd-context the delayed expanson is disabled (There is the rule or bug, that delayed expansion is done in the batch context when it's only a single line without parenthesis). I tested it with `%%cmdcmdline%%`. The stripping of the second part is done by the rule that parsing stops if an unescaped linefeed is found in line (in a block it's different) – jeb Jun 14 '13 at 07:18
  • @jeb - I think you misunderstood my explanation. I know the delayed expansion happens in the parent batch and not in the new cmd thread. Both lines are preserved if steps are taken to postpone the expansion until within new thread. However, I'm assuming the parser prepares the `cmd /S/D/c` statement before the delayed expansion, then the statement is expanded, then it is executed in the new thread. I also know the `cmd /S/D/c...` will stop at the first linefeed, so only 43 is piped to FINDSTR. But why no error from attempt to execute `abc"`? Where does `abc"` go? – dbenham Jun 14 '13 at 10:42
  • Ok I misunderstood your explanation :-) The `abc"` is _hard_ removed from the line by the unescaped linefeed. It's the same as in `echo 123%LF%abc`. The rest is removed by the parser after the percent expansion, but before the sepcial character phase begins – jeb Jun 14 '13 at 11:19
  • @jeb - ahh, simple enough. Thanks :-) – dbenham Jun 14 '13 at 11:34
3

Yes, I know this is a very old topic, but I just discovered it and I can't resist the temptation of post my solution:

@echo off
setlocal

set "str=45:abc"
set "var1=%str::=" & set "var2=%"
echo var1="%var1%"  var2="%var2%"

You may read full details of this method here.

Aacini
  • 59,374
  • 12
  • 63
  • 94
0

Here's a solution without nasty tricks for leading piece

    REM accepts userID@host
    setlocal enableDelayedExpansion
    set "str=%1"
    set "host=%str:*@=%"
    for /F "tokens=1 delims=@" %%F IN ("%str%") do set "user=%%F"
    echo user@host = %user%@%host%
    endlocal
0

In the Light of people posting all sorts of methots for splitting variables here i might as well post my own method, allowing for not only one but several splits out of a variable, indicated by the same symbol, which is not possible with the REM-Method (which i used for some time, thanks @jeb).

With the method below, the string defined in the second line is split into three parts:

setlocal EnableDelayedExpansion
set fulline=one/two/three or/more
set fulline=%fulline%//
REM above line prevents unexpected results when input string has less than two /

set line2=%fulline:*/=%
set line3=%line2:*/=%

set line1=!fulline:/%line2%=!
set line2=!line2:/%line3%=!

setlocal DisableDelayedExpansion

echo."%line1%"
echo."%line2%"
echo."%line3%"

OUTPUT:

"one"
"two"
"three or/more//"

i recommend using the last so-created partition of the string as a "bin" for the remaining "safety" split-characters.