0

For searches in Windows Registry using batch scripting, I have to loop through a few keys, make a comparison to determine which is the right one, and then update the key.

Iteration in a for loop seems impossible to break out of. I have seen that others are facing similar issues but there does not seem to be a simple solution. Here is a snippet that demonstrates the issue.

@echo off
echo.
echo Diet Favorites

set favorite="bananas"
for %%a in (apples, bananas, chocolates) do call :reviewList %%a
echo.
echo Processing completed.
goto end

:reviewList item
    set foundFavorite="false"
    call :chooseFavorite "%~1"
    if /I "%foundFavorite%"=="true" (
        echo found the favorite - %~1
        exit /b 0
    ) else (
        echo skip %~1
    )
    endlocal & goto :eof

:chooseFavorite item
    if "%~1"==%favorite% set "foundFavorite=true"
    endlocal & goto :eof

:end

chooseFavorite returns the favorite in an environment variable. The output shows that reviewList continues looping after the favorite is identified.

Diet Favorites
skip apples
found the favorite - bananas
skip chocolates

Processing completed.

The comparison is working, but if the exit worked as expected, chocolates diet option should not be listed at all. How do I neatly break out of the loop iteration?

wmclaxton
  • 35
  • 9
  • 3
    You are exiting your subroutine, but not the `for` loop. – Stephan Mar 25 '22 at 10:42
  • 1
    *N. B.:* `set favorite="bananas"` should read `set "favorite=bananas"` and `set foundFavorite="false"` should read `set "foundFavorite=false"` (so the quotes are not part of the value), and `if "%~1"==%favorite%` should read `if "%~1"=="%favorite%"` (as comparisons are literal and include quotes)… – aschipfl Mar 25 '22 at 12:46

2 Answers2

1

Check the return code of :reviewList in your loop, and break accordingly. You'll need to call properly exit /b 0 on non-breaker calls, and exit /b 1 (or any non-zero value) for breaker calls.

Try that:

@echo off
echo.
echo Diet Favorites

set favorite="bananas"
for %%a in (apples, bananas, chocolates) do (
    call :reviewList %%a || goto :break_loop
)
:break_loop
echo.
echo Processing completed.
goto end

:reviewList item
set foundFavorite="false"
call :chooseFavorite "%~1"
if /I "%foundFavorite%"=="true" (
    echo found the favorite - %~1
    exit /b 1
) else (
    echo skip %~1
)
endlocal
exit /b 0

:chooseFavorite item
if "%~1"==%favorite% set "foundFavorite=true"
endlocal & goto :eof

:end

Output is:

S:\Temp>test

Diet Favorites
skip apples
found the favorite - bananas

Processing completed.
Wisblade
  • 1,483
  • 4
  • 13
  • This was very helpful Wisblade. It works with very few changes in my code. But there were discrepancies with the other tips, such as placement of quotes. – wmclaxton Apr 04 '22 at 12:42
  • @wmclaxton Thanks. For curiosity's sake, which quotes are causing **issues**? I tried to modify as little as possible your original code, and I don't see where quotes could cause any particular problem in my corrected version... – Wisblade Apr 04 '22 at 12:59
  • All of the previous comments but especiallly aschipfl suggested using set "foundFavorite=false" or set "foundFavorite=" with surrounding quotes as a best practice. This was new to me. Although your code worked, it didn't include this feature. Which is why I credited magoo with the solution. In fact, the main problem with my code was failure to break out of the for loop, as stephan (tersely) point out. But I also learned about string comparisons. – wmclaxton Apr 05 '22 at 05:02
  • 1
    @wmclaxton Hmm, yeah. In fact, it doesn't matter: the test is done on "true", which is well-formed, and not on "false", which is in fact not really used (you can put here "do not care" instead of "false", code will work). It's quite the same reason that tells you, in C, to never test equality against "true" (all non-zero are "true"), but only with "false". It's a little bug, but it's not an issue. No reasons to not correct it properly anyway. I leave my answer untouched for education purpose. – Wisblade Apr 05 '22 at 07:19
1
@ECHO Off
SETLOCAL
echo.
echo Diet Favorites

set "favorite=bananas"
for %%a in (apples, bananas, chocolates) do call :reviewList %%a&IF DEFINED foundFavorite GOTO foundit
:foundit
echo.
echo Processing completed.
goto end

:reviewList param
set "foundFavorite="
call :chooseFavorite "%~1"
if DEFINED foundFavorite (
 echo found the favorite - %~1
) else (
 echo skip %~1
)
goto :eof

:chooseFavorite param
if "%~1"=="%favorite%" set "foundFavorite=%~1"
goto :eof

:end
GOTO :EOF

Since you don't use any setlocal commands in your posted code, the endlocals are superfluous.

This version sets foundFavorite to empty, and sets it to something (can be anything you like - I just chose the thing that was found, which is often convenient) when a match is found. if defined variable interprets the run-time status of the variable, so it can be used within a for loop.

BTW- try running your original code with echo ON. You would see that your if statements are actually executing if /i ""false""=="true" ...

This is why it's convention on SO to use the set "var=value" syntax for string-assignments. You can then apply quotes as required without worrying about where or whether to apply or remove quotes that may be in variables.

Magoo
  • 77,302
  • 8
  • 62
  • 84