goulashsoup
goulashsoup

Reputation: 3076

Batch - Delayed expansion doesn't work with surrounding if clause

In the following script I call a subroutine with a string, the maximal length of a substring of that string and a 3rd non-existing variable to get the substring back.

The script should check if the next character after the string with the maximal substrings length is a space and if yes, then cut the string by that space (delete space to) and returning the substring and change the passed string by cutting the substring part. Example:

string: "Hello World Bla"
substringLength: 5

CALL :get_substring string substringLength substring

=> string: "World Bla" (no space), substringLength: 5 (no change), substring: "Hello"

This works fine without if clause but it doesn't when i use an if clause, even if i use delayed expansion.

Here is the working code without if statement:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
    SET string=Hello World wasserschutzpolizei
    SET /A substringLength=5
    CALL :get_substring string substringLength substring
    ECHO !string!
    ECHO !substring!
    EXIT /B 0
ENDLOCAL

:get_substring
    SETLOCAL ENABLEDELAYEDEXPANSION

    SET "string=!%~1!"
    SET "substringLength=!%2!"
    SET nextChar=!string:~%substringLength%,1!

    REM IF "!nextChar!"==" " (
        SET substring=!string:~0,%substringLength%!
        ECHO !substring!
        SET /A cutSpaceCount=!substringLength!+1
        SET string=!string:~%cutSpaceCount%!

        ECHO !string!

        ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0
    REM ) ELSE (
    REM     Some other case    
    REM )

EXIT /B 0

This doesn't work when I comment in the if statement:

IF "!nextChar!"==" " (
    SET substring=!string:~0,%substringLength%!
    ECHO !substring!
    SET /A cutSpaceCount=!substringLength!+1
    SET string=!string:~%cutSpaceCount%!

    ECHO !string!

    ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0
) ELSE (
    REM Some other case    
)
  1. Why does the script doesn't work with an if-statement?
  2. How can fix it?

Be aware that my routine should also include an else statement which I cutted out because the problem is the same.

Upvotes: 2

Views: 542

Answers (3)

aschipfl
aschipfl

Reputation: 34909

As others already explained, the problem is the line:

SET string=!string:~%cutSpaceCount%!

because you use immediate expansion (%) for variable cutSpaceCount which is changed in the same logical line/block of code.

A possible solution is to use call like this:

    call set "string=%%string:~!cutSpaceCount!%%"

call introduces another variable expansion phase, so the sequence goes as follows:

  1. immediate expansion phase where %% becomes %:

    call set "string=%string:~!cutSpaceCount!%"
    
  2. then delayed expansion occurs (let us assume a sample value of 5):

    call set "string=%string:~5%"
    
  3. another immediate expansion phase introduced by call to finally get %string:~5%.

Upvotes: 1

Magoo
Magoo

Reputation: 80023

You seem to be very confused about the sequence of operations that occurs when delayed expansion has been invoked.

First, the value of var is substituted for %var%.

Then !var! is evaluated using the results.

The scope of this sequence of operations is one logical line, which may be one physical line or any number of physical lines continued with a terminal ^ or more commonly using a parenthesised sequence of lines.

In your mainline then,

CALL :get_substring string substringLength substring
ECHO !string!
ECHO !substring!
ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0

Since these statements are not within the same logical line, they will be individually evaluated, so !var!==%var%.

Within your subroutine (non-IF version),

    SET substring=!string:~0,%substringLength%!
    ECHO !substring!
    SET /A cutSpaceCount=!substringLength!+1
    SET string=!string:~%cutSpaceCount%!

    ECHO !string!

again are individual statements. The first set will first substitute for substringlength, and then execute SET substring=!string:~0,5! as a second operation.

Each of the echoes is a stand-alone statement, and the ! could (and preferably should) be replaced by %.

The set /a statement - well, set /a allows the current value of a variable to be used undecorated, so SET /A cutSpaceCount=substringLength+1 or SET /A cutSpaceCount=%substringLength%+1could be used here with no logical effect.

ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0 will be evaluated according to the values established by the previous code-sequence.

However when you add the if, the code is parenthesised and thus becomes one logical statement and acts differently.

The echoes then require ! because you want to display the modified values within the code-block. Since cutSpaceCount is not set at the start of the code-block, SET string=!string:~%cutSpaceCount%! will be evaluated as SET string=!string:~!

and then ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0 will duly substitute the values of the variables as they stood when the IFwas encountered

So, a replacement routine might be

:get_substring
SETLOCAL ENABLEDELAYEDEXPANSION

SET "string=!%~1!"
SET "substringLength=!%2!"
SET "substring=!string:~0,%substringLength%!"
SET "string=!string:~%substringLength%!"
IF "%string:~0,1%"==" " SET "string=%string:~1%"
ENDLOCAL & SET "%1=%string%" & SET "%3=%substring%"
EXIT /B 0

Upvotes: 3

Aacini
Aacini

Reputation: 67216

The problem is this line:

SET string=!string:~%cutSpaceCount%!

When this line is placed inside the IF command, then the value of cutSpaceCount is changed inside the code block (parentheses) of the IF, and hence it must be expanded via !cutSpaceCount! delayed expansion, not via a %cutSpaceCount% standard expansion.

You should use something like a "double delayed expansion", that is, similar to this construct:

SET string=!string:~!cutSpaceCount!!

Of course, this don't work, so the trick is use a for command to get the value of the first delayed expansion, and then use the FOR parameter to complete the second delayed expansion:

for /F %%c in ("!cutSpaceCount!") do SET "string=!string:~%%c!"

A similar problem happen when the final values in the subroutine are returned to the calling program. This is the final working code:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
    SET string=Hello World wasserschutzpolizei
    SET /A substringLength=5
    CALL :get_substring string substringLength substring
    ECHO !string!
    ECHO !substring!
    EXIT /B 0
ENDLOCAL

:get_substring
    SETLOCAL ENABLEDELAYEDEXPANSION

    SET "string=!%~1!"
    SET "substringLength=!%2!"
    SET "nextChar=!string:~%substringLength%,1!"

    IF "!nextChar!"==" " (
        SET "substring=!string:~0,%substringLength%!"
        ECHO !substring!
        SET /A cutSpaceCount=!substringLength!+1

        for /F %%c in ("!cutSpaceCount!") do SET "string=!string:~%%c!"

        ECHO !string!
        for /F "delims=" %%s in ("!string!") do for /F "delims=" %%b in ("!substring!") do (
            ENDLOCAL & SET "%1=%%s" & SET "%3=%%b" & EXIT /B 0
        )

    ) ELSE (
        ECHO Some other case    
    )

PS - You don't need to expand variable values in SET /A command. Instead of:

SET /A cutSpaceCount=!substringLength!+1

you may simply use:

SET /A cutSpaceCount=substringLength+1

Upvotes: 4

Related Questions