1

I have a variable (%~1) set to something like: Hello there this is a block: [C]Inside of Block[/C] Did you like it?

I want to have it separated into three variables:

Front=hello there this is a block: 
Block=Inside of Block
Back=Did you like it?

I tried using this:

:call
set var=%~1
set Front=%var:,"[C]"=&:%
set Back=%var:*"[/C]=%
Set var=%var:,"[/C]"=&:%
Set var=%var:*"[C]=%
set Inside=%var%

But it is not working. It may be because this is in a call section that is being called from inside a for loop (with EnableDelayedExpansion). But the whole string is set to each variable. Is this possible to do?

12
  • 1
    It's unclear to me what you intend with your substitutions, but if using poisenous characters like <>|& you'll have to enclose the whole set var=value pairs in double quotes -> Set "Front=%var:,"[C]"=&:%" (don't know what the inner double quotes are meant for).
    – LotPings
    Oct 2, 2018 at 18:19
  • Oh I think the quotes are mistakes. I’ll check.
    – Mark Deven
    Oct 2, 2018 at 18:22
  • If you have delayed expansion enabled then % needs to be replaced by !
    – DavidPostill
    Oct 2, 2018 at 20:06
  • @Worthwelle:  Please look and see what you overlooked. Oct 2, 2018 at 20:07
  • Mark Dodsons:  Thanks for showing us the code you’ve written, but please don’t just throw code at us and say “it is not working.”  What is it doing?  What have you learned from your debugging?  (You have tried debugging this on your own, right?)  Is var getting the desired value from %~1?  What values are the other assignments yielding?  (The echo statement is your friend.)  For that matter, what values are you expecting to get from the assignments?  I don’t understand them either; please explain the logic of your code. … (Cont’d) Oct 2, 2018 at 20:07

2 Answers 2

4

I would have been helpful had you included a link to something that explains the technique you are attempting to use. Thankfully, I am familiar with the technique.

You are attempting to use variable expansion find/replace to inject a comment into a string so that you can extract the beginning of the string, up until some substring. I am more familiar with using REM, but your use of a : label as a pseudo comment should work as well.

I'll show a quick example of how this can work. I have ECHO ON at strategic lines so that you can see how the substitution works. This requires that I use REM instead of : because labels are not ECHOed.

@echo off
setlocal
set "var=String Before<split here>String After"
echo on
set "Before=%var:<split here>="&rem %
@set before
set "After=%var:*<split here>=%"
@set after

--OUTPUT--

C:\test>set "Before=String Before"  & rem String After
Before=String Before

C:\test>set "After=String After"
After=String After

The extra spaces around the & are an artifact of how cmd.exe echoes the line, they are not really introduced by the find/replace. But you should be able to see how the technique works.

I don't understand why you have included commas and quotes in your search string - they are not there in your starting string, so nothing will get replaced.

It is important that you use quotes when you save the initial value of %~1 so as to protect against poison characters.

Finally, it is also critical to have quotes in your subsequent assignments. The first quote is before the variable name, and the closing quote is injected by the replace term, just prior to the &:.

Because you are using : instead of REM , there is no need to introduce a space after the :.

Here is working code. Note that I eliminated the need for an extra var variable by using Back for the temporary values until I am ready to get the final Back value.

@echo off
setlocal
call :extract "Hello there this is a block: [C]Inside of Block[/C] Did you like it?"
echo Front  = "%Front%"
echo Inside = "%Inside%"
echo Back   = "%Back%"
exit /b

:extract
set "Back=%~1"
set "Front=%Back:[C]="&:%
set "Back=%Back:*[C]=%"
set "Inside=%Back:[/C]="&:%
set "Back=%Back:*[/C]=%"
exit /b

-- OUTPUT --

Front  = "Hello there this is a block: "
Inside = "Inside of Block"
Back   = " Did you like it?"

Note my use of quotes in the output - they are not in the actual stored values. Rather they are introduced by the ECHO command to show the existence of the trailing/leading space, and also they would protect against poison characters.

The above technique has a few restrictions:

  • The %~1 value should not contain any quotes, else you run the risk of poison characters corrupting the result
  • The %~1 value cannot contain newline (0x0A) or carriage return (0x0D).
  • The substrings that you are replacing ([C] and [/C] in your case) must not begin with ~, *, or %.
  • The substrings that you are replacing must not contain = anywhere within them

Here is a totally unrelated JREPL.BAT solution

If you like working with regular expressions, then you could use my JREPL.BAT - a pure script utility (hybrid JScript/batch) that runs natively on any Windows machine from XP onward, no need for any 3rd party .exe file.

The JREPL solution will be a bit slower than pure batch for this application, but it has two big advantages:

  • The logic is more straight forward, as long as you understand regular expressions
  • There are no character limits. %~1 still cannot contain carriage return or linefeed. But a variable may, and JREPL will work just fine with those characters.

If you only need to extract a single value, then the solution is really simple - JREPL has the ability to store the result in an environment variable. The code below will capture the value inside your block:

@echo off
setlocal
call :extract "Hello there this is a block: [C]Inside of Block[/C] Did you like it?"
echo Inside = "%Inside%"
exit /b

:extract
set "Inside=%~1"
call jrepl "\[C](.*)\[/C]" "$txt=$1" /s Inside /jmatchq /rtn Inside
exit /b

-- OUTPUT --

Inside = "Inside of Block"

But you want to capture 3 values. You could make three separate JREPL calls, but that would be inefficient. In the code below I insert VariableName= and newlines in the appropriate places, and let FOR /F iterate and store the three results.

@echo off
setlocal
call :extract "Hello there this is a block: [C]Inside of Block[/C] Did you like it?"
echo Front  = "%Front%"
echo Inside = "%Inside%"
echo Back   = "%Back%"
exit /b

:extract
set "Front=%~1"
for /f "delims=" %%A in (
  'jrepl "^|\[C]|\[/C]" "Front=|\nInside=|\nBack=" /s Front /t "|" /xseq'
) do set "%%A"
exit /b

-- OUTPUT --

Front  = "Hello there this is a block: "
Inside = "Inside of Block"
Back   = " Did you like it?"

Extensive help is built into the JREPL utility.

  • JREPL /? will list all the help.
  • JREPL /?options will briefly summarize all available options
  • JREPL /?/T will describe the Translate option that I used. You can do the same for /?/XSEQ and /?/S

You will likely find many uses for JREPL once have it in your arsenal of tools. JREPL really shines when it comes to manipulating text files - it is much faster and more powerful than any pure batch solution.

3
  • Yeah I wasn’t able to find where I had gotten that logic, but I found similar code in another script that didn’t work since it required stuff that I don’t have. The commas and stuff were left over and I just didn’t know what was the string and what was code.
    – Mark Deven
    Oct 2, 2018 at 21:30
  • will this work even with characters like :,;} ?
    – Mark Deven
    Oct 3, 2018 at 0:29
  • @MarkDodsons - I added the limitations to the answer, and I added a totally unrelated JREPL.BAT solution that removes the limitations.
    – dbenham
    Oct 3, 2018 at 7:10
1

In general, batch files are not very efficient for processing complex things like this. It is also not possible to use variable replacement the way you tried.

That being said, this solution should work for any strings that contain a single block or contain no blocks at all. This will not work for multiple blocks. It will also only work for single character block tags (i.e. [C], [b], [9]). It also won't work if you have [ characters in your text that aren't part of blocks.

:Split
for /f "delims=[ tokens=1-3" %%a IN ("%~1") DO (
    CALL:Set "%%~a" "%%~b" "%%~c"
)
GOTO:EOF

:Set
SET Front=%~1
SET Inside=%~2
SET Back=%~3
SET Block=

IF NOT "%Inside%"=="" (
    SET Block=%Inside:~0,1%
    SET Inside=%Inside:~2%
)
IF NOT "%Back%"=="" (
    SET Back=%Back:~3%
)
GOTO:EOF

The Split call will split the string by the [ character and pass this to the Set call. The Set call will then remove the beginning and ending block tags.

Further reading: Variable Edit/Replace - SS64

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .